이 기사는 Huawei Cloud 커뮤니티 " 3월 독서 주간·당신이 모르는 자바스크립트 | ES6 생성기, 겉으로는 동기식 비동기 프로세스 제어 표현 스타일 "에서 공유되었습니다. 저자: Ye Yiyi.
빌더
풀런을 중단하다
JavaScript 개발자는 코드에서 거의 보편적으로 의존한다는 가정이 있습니다. 함수가 실행되기 시작하면 끝까지 실행되며 다른 코드는 함수를 중단하거나 그 사이에 삽입할 수 없습니다.
ES6에는 이러한 실행-끝 기능을 따르지 않는 새로운 함수 유형이 도입되었습니다. 이 새로운 유형의 함수를 생성기라고 합니다.
var x = 1; 함수 foo() { x++; bar(); // <-- 이 줄은 x++ 및 console.log(x) 문 사이에서 실행됩니다. console.log('x:', x); } 함수 바() { x++; } foo(); // x: 3
bar()가 없으면 어떻게 되나요? 분명히 결과는 3이 아니라 2가 될 것입니다. 최종 결과는 3이므로 bar()는 x++와 console.log(x) 사이에서 실행됩니다.
그러나 JavaScript는 선점형도 아니고 (아직) 멀티스레드도 아닙니다. 그러나 foo() 자체가 코드의 이 지점에서 일시 중지를 나타낼 수 있는 경우 협력 방식(동시성)으로 이러한 중단을 구현하는 것이 여전히 가능합니다.
협력적 동시성을 구현하는 ES6 코드는 다음과 같습니다.
var x = 1; 함수* foo() { x++; 양보; // 일시 중지! console.log('x:', x); } 함수 바() { x++; } //이 생성기를 제어하기 위해 iterator를 구성합니다. var = foo(); //여기서 foo()를 시작하세요! it.next(); console.log('x:', x); // 2 술집(); console.log('x:', x); // 삼 it.next(); // x: 3
- it = foo() 작업은 생성기 *foo()를 실행하지 않고 실행을 제어하는 반복자(iterator)만 구성합니다.
- *foo()는 첫 번째 it.next() 호출이 끝나는 지점에서 Yield 문에서 일시 중지됩니다. 이 시점에서 *foo()는 여전히 실행 중이고 활성 상태이지만 일시 중지된 상태입니다.
- 마지막 it.next() 호출은 일시 중지된 위치에서 *foo() 생성기의 실행을 재개하고 x의 현재 값인 3을 사용하는 console.log(..) 문을 실행합니다.
생성기는 한 번 이상 시작하고 중지할 수 있지만 반드시 완료할 필요는 없는 특수한 유형의 기능입니다.
입력과 출력
생성기 함수는 함수의 일부 기본 속성을 여전히 갖고 있는 특수 함수입니다. 예를 들어 매개변수(예: 입력)와 반환 값(예: 출력)을 계속 허용할 수 있습니다.
함수* foo(x, y) { x * y를 반환합니다. } var it = foo(6, 7); var res = it.next(); res.값; // 42
실제 매개변수 6과 7을 각각 매개변수 x와 y로 *foo(..)에 전달합니다. *foo(..)는 호출 코드에 42를 반환합니다.
여러 반복자
반복자를 빌드할 때마다 실제로 생성기의 인스턴스가 암시적으로 생성됩니다. 이 반복기를 통해 제어되는 생성기 인스턴스입니다.
동일한 생성기의 여러 인스턴스가 동시에 실행될 수 있으며 서로 상호 작용할 수도 있습니다.
함수* foo() { var x = 산출량 2; z++; var y = 산출량 x * z; console.log(x, y, z); } var z = 1; var it1 = foo(); var it2 = foo(); var val1 = it1.next().value; // 2 <-- 산출량 2 var val2 = it2.next().value; // 2 <-- 산출량 2 val1 = it1.next(val2 * 10).value; // 40 <-- x:20, z:2 val2 = it2.next(val1 * 5).value; // 600 <-- x:200, z:3 it1.next(val2 / 2); // y:300 // 20300 3 it2.next(val1 / 4); // y:10 // 200 10 3
실행 프로세스를 간략하게 요약하면 다음과 같습니다.
(1) *foo()의 두 인스턴스가 동시에 시작되고, 두 next()가 각각 Yield 2 문에서 값 2를 가져옵니다.
(2) 2 * 10인 val2 * 10이 첫 번째 생성기 인스턴스 it1로 전송되므로 x는 값 20을 얻습니다. z는 1에서 2로 증가한 다음 20 * 2가 Yield를 통해 방출되어 val1을 40으로 설정됩니다.
(3) 40 * 5인 val1 * 5가 두 번째 생성기 인스턴스 it2로 전송되므로 x는 값 200을 얻습니다. z는 다시 2에서 3으로 증가한 다음 200 * 3이 Yield를 통해 방출되고 val2가 600으로 설정됩니다.
(4) 600/2인 val2/2가 첫 번째 생성기 인스턴스 it1로 전송되므로 y는 값 300을 얻은 다음 xyz 값은 각각 20300 3으로 인쇄됩니다.
(5) 40/4인 val1/4가 두 번째 생성기 인스턴스 it2로 전송되므로 y는 값 10을 얻은 다음 xyz의 값은 각각 200 10 3으로 인쇄됩니다.
발전기는 가치를 생산합니다
생산자와 반복자
각 값이 이전 값과 특정 관계를 갖는 일련의 값을 생성한다고 가정해 보겠습니다. 이를 달성하려면 생성된 마지막 값을 기억할 수 있는 상태 저장 생산자가 필요합니다.
반복자는 생산자로부터 일련의 값을 단계별로 가져오기 위한 잘 정의된 인터페이스입니다. JavaScript 반복자의 인터페이스는 생산자로부터 다음 값을 얻고 싶을 때마다 next()를 호출하는 것입니다.
숫자 시퀀스 생성기에 대해 표준 반복자 인터페이스를 구현할 수 있습니다.
var 무엇인가 = (함수 () { var nextValid; 반품 { // for..of 루프가 필요합니다. [Symbol.iterator]: 함수 () { 이거 돌려줘; }, // 표준 반복자 인터페이스 메소드 다음: 함수 () { if (nextVal === 정의되지 않음) { nextVal = 1; } 또 다른 { nextVal = 3 * nextVal + 6; } return { 완료: false, 값: nextVal }; }, }; })(); 뭔가.다음().값; // 1 뭔가.다음().값; // 9 뭔가.다음().값; // 33 뭔가.다음().값; // 105
next() 호출은 객체를 반환합니다. 이 객체에는 두 가지 속성이 있습니다. done은 반복기의 완료 상태를 식별하는 부울 값입니다. 값은 반복 값을 배치합니다.
반복 가능한
iterable은 해당 값을 반복할 수 있는 반복자를 포함하는 객체입니다.
ES6부터 iterable에서 iterator를 추출하는 방법은 iterable이 이름이 특수한 ES6 기호 값 Symbol.iterator인 함수를 지원해야 한다는 것입니다. 이 함수가 호출되면 반복자를 반환합니다. 필수는 아니지만 일반적으로 각 호출은 새 반복자를 반환합니다.
var a = [1, 3, 5, 7, 9]; for (var v of a) { console.log(v); } // 1 3 5 7 9
위 코드 조각의 a는 반복 가능합니다. for..of 루프는 자동으로 Symbol.iterator 함수를 호출하여 반복자를 만듭니다.
for(무언가의 var v) { .. }
for..of 루프는 반복 가능한 항목을 기대하므로 해당 Symbol.iterator 함수를 찾아 호출합니다.
생성기 반복자
생성자는 값의 생성자로 간주될 수 있습니다. 반복자 인터페이스의 next() 호출을 통해 한 번에 하나의 값을 추출합니다.
생성기 자체는 반복 가능하지 않습니다. 생성기를 실행하면 반복자가 생성됩니다.
함수 *foo(){ .. } var = foo();
이전의 무한 숫자 시퀀스 생성자는 다음과 유사하게 생성기를 통해 구현할 수 있습니다.
함수* 뭔가() { var nextValid; 동안 (참) { if (nextVal === 정의되지 않음) { nextVal = 1; } 또 다른 { nextVal = 3 * nextVal + 6; } nextVal을 산출; } }
생성기가 각 산출에서 일시 중지되기 때문에 *something() 함수의 상태(범위)가 유지됩니다. 즉, 호출 간에 변수 상태를 유지하기 위해 클로저가 필요하지 않다는 의미입니다.
비동기 반복기 생성기
함수 foo(x, y) { ajax('http://some.url.1/? x=' + x + '&y=' + y, 함수(err, 데이터) { 만약 (오류) { // *main()에 오류를 던집니다. it.throw(err); } 또 다른 { //수신된 데이터로 *main()을 복원합니다. it.next(데이터); } }); } 함수* 메인() { 노력하다 { var text = 산출 foo(11, 31); console.log(텍스트); } 잡기 (오류) { console.error(err); } } var = 메인(); //여기서 시작하세요! it.next();
Yield foo(11,31)에서는 foo(11,31)이 먼저 호출되는데, 이는 값을 반환하지 않습니다(즉, 정의되지 않음을 반환함). 따라서 데이터를 요청하기 위한 호출이 이루어지지만 나중에 실제로 수행되는 작업은 항복이 정의되지 않습니다.
여기서 Yield는 메시지 전달이라는 의미로 사용되지 않고 일시 중지/차단을 구현하기 위한 흐름 제어에만 사용됩니다. 실제로 메시지 전달은 계속되지만 생성기가 작업을 재개한 후에는 단방향 메시지 전달만 됩니다.
foo(..)를 살펴보세요. 이 Ajax 요청이 성공하면 다음을 호출합니다.
it.next(데이터);
그러면 응답 데이터를 사용하여 생성기가 다시 시작됩니다. 즉, 일시 중지된 항복 표현식이 이 값을 직접 수신했음을 의미합니다. 그런 다음 생성기 코드가 계속 실행됨에 따라 이 값이 로컬 변수 text에 할당됩니다.
요약하다
이 글의 주요 내용을 요약해 보겠습니다.
- Generator는 ES6의 새로운 함수 유형입니다. 일반 함수처럼 항상 끝까지 실행되지는 않습니다. 대신 생성기를 실행하는 동안 일시 중지하고(상태를 완전히 보존함) 나중에 일시 중지된 위치에서 다시 시작할 수 있습니다.
- Yield/next(..) 쌍은 제어 메커니즘일 뿐만 아니라 양방향 메시지 전달 메커니즘이기도 합니다. 생산하다 .. 표현식은 본질적으로 일시 중지하고 값을 기다리며, 후속 next(..) 호출은 일시 중지된 항복 표현식에 값(또는 암시적으로 정의되지 않음)을 반환합니다.
- 비동기 제어 흐름과 관련하여 생성기의 주요 장점은 생성기 내부의 코드가 자연스럽게 동기/순차적 방식으로 작업을 표현하는 일련의 단계라는 것입니다. 비결은 가능한 비동기성을 항복 키워드 뒤에 숨기고 생성기의 반복자를 제어하는 코드 부분으로 비동기성을 이동하는 것입니다.
- 생성기는 비동기 코드에 대해 순차, 동기, 차단 코드 패턴을 유지하므로 뇌가 코드를 보다 자연스럽게 따라갈 수 있어 콜백 기반 비동기의 두 가지 주요 결함 중 하나가 해결됩니다.
화웨이 클라우드의 신기술에 대해 빨리 알아보고 팔로우하려면 클릭하세요~
JetBrains 2024(2024.1)의 첫 번째 메이저 버전 업데이트는 오픈소스 인데 Microsoft도 비용을 지불할 계획인데 아직도 오픈소스라는 비판을 받는 이유는 무엇일까요? [복구] Tencent Cloud 백엔드 충돌: 콘솔에 로그인한 후 많은 서비스 오류가 발생하고 데이터가 없습니다. 독일도 "독립적으로 제어 가능"해야 합니다. 주 정부는 30,000대의 PC를 Windows에서 Linux deep-IDE로 마이그레이션하여 마침내 달성했습니다. 부트스트래핑! Visual Studio Code 1.88이 출시되었습니다. Tencent는 Switch를 "생각하는 학습 기계"로 전환했습니다. RustDesk 원격 데스크톱이 시작되고 웹 클라이언트를 재구성합니다. WeChat의 SQLite 기반 오픈 소스 터미널 데이터베이스인 WCDB가 크게 업그레이드되었습니다.