[es6] のジェネレーター


1. ジェネレーターとは何ですか?

Generator関数はES6が提供する非同期プログラミングソリューションで、最大の特徴は関数の実行権を引き渡すことです。

通常の関数と異なるのは、関数を実行するには next() を呼び出す必要があることです。

1.1 通常の関数記述とは異なり、2つの違いがあります

  • 関数と関数名の間に(*)記号があり、
    通常の関数と異なり実行を一時停止できるため*区別が追加されています。
  • yield ステートメントはジェネレーター関数本体内でさまざまな内部状態を定義するために使用されますが、実際にはデータが異なります。
    関数内の状態は関数内の値であり、時間ごとに異なります。

基本的に、ジェネレーター関数全体は、カプセル化された非同期タスク、または非同期タスクのコンテナーです。
yield コマンドは、異なる非同期フェーズ間の境界線であり、yield がリターンとみなされる場合もあります。

2. 発電機の使用

2.1 書き込み方法

  1. 関数名とメソッドの間には数字が入ります*が、配置は特に指定されておらず、通常は関数の後に続きます。
  1. Generator 関数を呼び出すと、関数は実行されず、内部状態ポインター (トラバーサー オブジェクト) を指すオブジェクトを返します。
function* test(){
    
    
	let n = 1;
	yield n;
	yield++n;
	yield--n;
	return n;
}

let t = test();
console.log(t);  //test {<suspended>}

返されるのはオブジェクトであり、next、return、throw メソッドがあります。
ここに画像の説明を挿入します

  1. 値を出力したい場合は、next メソッドを呼び出してポインターを次の状態に変更する必要があります。つまり、各呼び出しは前のポインター状態 (yield) から開始され、次の yield または return ステートメントが検出されたときに終了します。つまり、セグメントごとに実行され、yield が一時停止され、次に実行が継続されます。

以下は 6 回、1 つの収量と 1 つの状態と呼ばれます。オブジェクトを返します。value は値を表し、done はトラバーサルが完了したかどうかを表します (true は完了、false は完了していません)。

Done が true の場合はすでに関数の終了を表しており、何度か出力される値は未定義になります。

console.log(t.next());  //{value: 1, done: false}
console.log(t.next());  //{value: 2, done: false}
console.log(t.next());  //{value: 1, done: false}
console.log(t.next());  //{value: 1, done: true}
console.log(t.next());  //{value: undefined, done: true}
console.log(t.next());  //{value: undefined, done: true}

3. 利回りステートメント

yield ステートメントは一時停止フラグです。

3.1 利回りとリターン

同じ点:

  • 次の式の値を返すことができます。

違い:

  • yield 関数が実行を一時停止すると、次回はその位置から逆方向に実行が継続され、関数には複数の yield ステートメントを含めることができます。
  • Return は関数の終了と同じであり、関数の return は 1 つだけです。

3.2 注意事項

  1. yield 後の n+1 は実行されません。次のメソッドが呼び出された場合にのみ実行されます。
function* test() {
    
    
	let n = 1;
	yield n + 1;
	console.log(n);
}
test();   //此时打印是空白的

//必须调用next方法才会执行
console.log(test().next());  //{value: 2, done: false}

2. Yield を使用する必要はありませんが、それを実行するには next メソッドを呼び出す必要があります。

function* test() {
    
    
	let n = 1;
	console.log(n);
}
let t = test();
console.log(t.next());
  1. yield ステートメントは通常の関数では使用できません。
function test() {
    
    
	yield 1;
}
console.log(test());  // Unexpected number

3.3 yield* ステートメント

Generator 関数は内部で別の Generator 関数を呼び出しますが、これはデフォルトでは無効です。

function* A(){
    
    
	yield 1;
	yield 2;
}
function* B(){
    
    
	yield 3;
	A();
	yield 4;
}
for(let i of B()){
    
    
	console.log(i)
}
//3
//4

B関数内でA()を呼び出していますが、3,4しか出力できません。呼び出したい場合は、先頭に yield* ステートメントを使用する必要があります。

function* A(){
    
    
	yield 1;
	yield 2;
}
function* B(){
    
    
	yield 3;
	yield* A();
	yield 4;
}
for(let i of B()){
    
    
	console.log(i)
}
//3
//1
//2
//4

Yield* は for...of.... の短縮形です。for...of... はイテレータ インターフェイス (配列、配列のようなオブジェクト、文字列、ノードリストなど) を使用してデータ構造を横断できることがわかっています。

3.4 収量*の適用

  1. 配列の平坦化
let a = [1,[1,2],[1,2,3]]
function* iterTree(arr){
    
    
	if(Array.isArray(arr)){
    
    
		for(let i = 0;i<arr.length;i++){
    
    
			yield* iterTree(arr[i]);
		}
	}else{
    
    
		yield arr;
	}	
}
for(let i of iterTree(a)){
    
    
	console.log(i)
}
  1. 完全なバイナリ ツリーを走査する
// 构建树
function Tree(left,middle,right){
    
    
	this.left = left;
	this.middle = middle;
	this.right = right;
}
// 中序遍历函数
function* inorder(t){
    
    
	if(t){
    
    
		yield* inorder(t.left);
		yield t.middle;
		yield* inorder(t.right);
	}
}
// 生成二叉树
function make(array){
    
    
	if(array.length == 1) return new Tree(null,array[0],null);
	return new Tree(make(array[0]),array[1],make(array[2]));
}
let tree = make([[['a'],'b',['c']],'d',[['e'],'f',['g']]]);
// 遍历二叉树
var result = [];
for(let node of inorder(tree)){
    
    
	result.push(node);
}
console.log(result)   //['a', 'b', 'c', 'd', 'e', 'f', 'g']

4.次の方法

next メソッドは、ポインタが次の状態を指すようにします。for...of traversal に加えて、next を呼び出して、yield 値を出力する必要があります。

4.1 パラメータ

yield ステートメント自体は値を返さないか、常に未定義を返します。次のメソッドはパラメーターを受け取ることができ、これは yield ステートメントの戻り値として使用されます。

function* test(x) {
    
    
	var a = yield x-1;
	var b = yield (a+2);
	return a+b;
}
let t = test(4);
console.log(t.next())     //{value: 3, done: false}
console.log(t.next(10))   //{value: 12, done: false}
console.log(t.next(4))    //{value: 14, done: true}

ステップ 1: 4 を渡す、x-1=3、3 として出力されます。
ステップ 2: 10 を渡す、このパラメータは yield ステートメントの戻り値として使用されます。つまり、a の値は 10、a です。 +2、12 として出力されます。
ステップ 3: 4 を渡します。前のパラメータ b の値は 4、a の値は前の 10、a+b は 14 です。

4.2 動作ロジ​​ック

  • yield 式に遭遇すると、後続の操作の実行が一時停止され、yield の直後の式の値が、返されたオブジェクトの value 属性値として使用されます。
  • 次回 next メソッドが呼び出されたとき、次の yield 式が見つかるまで実行が継続されます。
    新しい yield 式が見つからない場合は、関数の終わりから return ステートメントまで実行され、return ステートメントに続く式の値が、返されたオブジェクトの value 属性として使用されます。
  • 関数に return ステートメントがない場合、返されるオブジェクトの value 属性は未定義です。

5. 非同期ソリューション

いくつかの非同期ソリューションがあることは以前からわかっていました。

  • コールバック関数
    いわゆるコールバック関数は、タスクの 2 番目の段落を別の関数に記述し、タスクが再実行されるときにこの関数を呼び出します。
fs.readFile('/etc/fstab', function (err, data) {
    
    
  if (err) throw err;
  fs.readFile('/etc/shells', function (err, data) {
    
    
    if (err) throw err;
    console.log(data);
  });
});

readFile 関数の 3 番目のパラメータはコールバック関数です。このコールバック関数は、オペレーティング システムが /etc/fstab ファイルを返すまで実行されません。

  • Promise オブジェクト
    Promise は、コールバック地獄を解決するために作成され、コールバック関数のネストをチェーン呼び出しに変更します。
const fs = require('fs');

const readFile = function (fileName) {
    
    
  return new Promise(function (resolve, reject) {
    
    
    fs.readFile(fileName, function(error, data) {
    
    
      if (error) return reject(error);
      resolve(data);
    });
  });
};

readFile('/etc/fstab').then(data =>{
    
    
    console.log(data)
    return readFile('/etc/shells')
}).then(data => {
    
    
    console.log(data)
})
  • 非同期/待機
const asyncReadFile = async function () {
    
    
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
  • ジェネレーター関数の
    yield 式は関数の実行を一時停止でき、次のメソッドを使用して関数の実行を再開できるため、ジェネレーター関数は非同期タスクの同期に非常に適しています。
const gen = function* () {
    
    
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

違い:
上記のコードを介して分析し、promise、generator、async/await を比較します。

  • Promise と async/await は、非同期操作を処理するために特に使用されます。

  • Generator は非同期用に設計されておらず、他の機能 (オブジェクトの反復、制御出力、Interator インターフェイスの展開など) を備えています。

  • Promise のコード作成は Generator や async に比べて複雑で、可読性も若干劣ります。

  • 非同期状況を処理するには、ジェネレーターと async を Promise オブジェクトと組み合わせる必要があります

  • async は本質的にジェネレーターの糖衣構文であり、ジェネレーター関数を自動的に実行することと同等です。

  • Async は使い方が簡単で、同期形式で非同期コードを記述することが、非同期プログラミングの究極のソリューションです。

6. ジェネレーター関連のインタビューの質問

1. Iterator、Generator、Async/Await についてどのように理解していますか?

  • 1. イテレータ

Iterator はループ インターフェイスであり、このインターフェイスを実装するデータはすべて for of ループで走査できます。

一般的に使用される for...of ループはすべて、ループされたオブジェクトの特別な関数 Iterator を呼び出すことによって実装されます。ただし、この関数は以前は隠されており、アクセスできませんでした。Symbol から導入された後は、Symbol を通じて直接読み取ることができます.iterator. この特別な関数を作成します。

ループ ステートメントに関しては、ループされるオブジェクトが何であるかは関係なく、data[Symbol.iterator] 関数を呼び出し、戻り値に基づいてループすることだけを担当します。

let obj = {
    
    }
obj[Symbol.iterator] = function() {
    
    
	let index = 1;
	return {
    
    
		next() {
    
    
			return {
    
    
				done: index > 5,value: index++
			}
		}
	}
}
for (var i of obj) {
    
    
	console.log(i,obj);
}

ここに画像の説明を挿入します

  • 2. 発電機

Generator は、より柔軟な Iterator とみなすことができ、相互に置き換えることができます。ただし、Generator は、yield を通じていつでも一時停止できるため、プロセス制御や状態管理には非常に便利ですが、Iterator ではさらに多くの記述が必要になる場合があります。コードも同じことを行います。

  • 3. Async/Await
    async はジェネレーターの構文糖です。
    • 非同期は * に対応します
    • await は yield に対応します
async function count () {
    
    
  let a = await 1;
  let b = await 2;
  return a+b
}

2. Generator はどのようにして中断と回復を実現しますか?

実行の中断および再開の機能は、yield 式を使用して実装されます。

Generator 関数が呼び出されると、すぐには実行されず、イテレータ オブジェクトが返されます。iterator オブジェクトの next() メソッドが呼び出されるたびに、Generator 関数は、次の yield 式に遭遇するか関数が終了するまで、最後の実行位置から実行を続けます。このとき、Generator 関数は現在の値と実行ステータスを含むオブジェクトを返します。value 属性は yield 式の結果を示し、done 属性は実行が完了したかどうかを示します。


要約する

ジェネレーター関数は、関数の実行をより適切に制御できる非同期プログラミング ソリューションです。収量は同期的であり、値は next() を呼び出すことで出力できます。

この関数はオブジェクトを返し、next メソッドも値と Done (走査が完了したかどうか) を含むオブジェクトを返します。

next は yield ステートメントに相当し、lastned が true の場合、再度 next メソッドが呼び出され、値はすべて未定義になります。for...of を使用してすべてを走査することができます。原則として、データ構造内にイテレータ インターフェイスが存在します。

おすすめ

転載: blog.csdn.net/2201_75499330/article/details/132359616