JavaScriptで「yield」キーワードについて聞いたのですが、それに関するドキュメントが非常に貧弱でした。誰かが私にその使用法とその使用目的を説明する(または説明するサイトを推奨する)ことができますか?
14 に答える
回答が遅れて、おそらく誰もが今知っているでしょうyield
が、いくつかのより良いドキュメントが出てきました。
James Longによる「Javascript'sFuture:Generators」の例を、公式のHarmony標準に適合させます。
function * foo(x) {
while (true) {
x = x * 2;
yield x;
}
}
「fooを呼び出すと、次のメソッドを持つGeneratorオブジェクトが返されます。」
var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16
だからyield
、ある種のようなものreturn
です:あなたは何かを取り戻します。 return x
の値を返しますがx
、yield x
次の値に向かって反復するメソッドを提供する関数を返します。反復中に中断する可能性のある、メモリを大量に消費する可能性のある手順がある場合に役立ちます。
それは本当に簡単です、これはそれがどのように機能するかです
yield
キーワードは、非同期でいつでも関数を一時停止および再開するのに役立ちます。- さらに、ジェネレーター関数から値を返すのに役立ちます。
この単純なジェネレーター関数を使用してください。
function* process() {
console.log('Start process 1');
console.log('Pause process2 until call next()');
yield;
console.log('Resumed process2');
console.log('Pause process3 until call next()');
let parms = yield {age: 12};
console.log("Passed by final process next(90): " + parms);
console.log('Resumed process3');
console.log('End of the process function');
}
let _process = process();
_process.next()を呼び出すまで、コードの最初の2行は実行されません。その後、最初のyieldは関数を一時停止します。次の一時停止ポイント(yieldキーワード)まで関数を再開するには、 _process.next()を呼び出す必要があります。
複数のyieldは、単一の関数内のjavascriptデバッガーのブレークポイントであると考えることができます。次のブレークポイントに移動するように指示するまで、コードブロックは実行されません。(注:アプリケーション全体をブロックすることなく)
ただし、yieldはこの一時停止と再開の動作を実行しますが、前の関数に従っていくつかの結果を返す{value: any, done: boolean}
こともでき
ますが、値は出力されていません。前の出力を調べると{ value: undefined, done: false }
、値がundefinedの場合と同じように表示されます。
イールドキーワードを掘り下げてみましょう。オプションで、式を追加し、デフォルトのオプション値を割り当てるように設定できます。(公式ドキュメント構文)
[rv] = yield [expression];
式:ジェネレーター関数から返す値
yield any;
yield {age: 12};
rv:ジェネレータのnext()メソッドに渡されたオプションの値を返します
このメカニズムを使用して、パラメーターをprocess()関数に渡すだけで、さまざまなyieldパーツを実行できます。
let val = yield 99;
_process.next(10);
now the val will be 10
使用法
- 遅延評価
- 無限のシーケンス
- 非同期制御フロー
参照:
MDNドキュメントはかなり良いです、IMO 。
yieldキーワードを含む関数はジェネレーターです。これを呼び出すと、その正式なパラメーターは実際の引数にバインドされますが、その本体は実際には評価されません。代わりに、generator-iteratorが返されます。ジェネレータイテレータのnext()メソッドを呼び出すたびに、反復アルゴリズムを介して別のパスが実行されます。各ステップの値は、yieldキーワードで指定された値です。歩留まりは、アルゴリズムの各反復間の境界を示す、ジェネレーターとイテレーターのバージョンのリターンと考えてください。next()を呼び出すたびに、ジェネレーターコードはyieldに続くステートメントから再開します。
Nick Sotirosの答え(私は素晴らしいと思います)を単純化/詳しく説明します。どのようにコーディングを開始するかを説明するのが最善だと思いますyield
。
私の意見では、を使用する最大の利点はyield
、コードで見られるネストされたコールバックの問題をすべて排除できることです。最初はどのように理解するのが難しいので、私はこの答えを書くことにしました(私自身のために、そしてうまくいけば他の人のために!)
それを行う方法は、コルーチンのアイデアを導入することです。コルーチンは、必要なものが得られるまで自発的に停止/一時停止できる機能です。javascriptでは、これは。で示されfunction*
ます。function*
関数のみが使用できますyield
。
ここにいくつかの典型的なJavaScriptがあります:
loadFromDB('query', function (err, result) {
// Do something with the result or handle the error
})
すべてのコード(明らかにこの呼び出しを待つloadFromDB
必要がある)がこの見苦しいコールバック内にある必要があるため、これは不格好です。これはいくつかの理由で悪いです...
- すべてのコードは、1レベルインデントされています
})
あなたはどこでも追跡する必要があるこの終わりを持っています- このすべての余分な
function (err, result)
専門用語 - 値を割り当てるためにこれを行っているかどうかは明確ではありません
result
一方、を使用すると、これらすべてを1行yield
で実行できます。これは優れたコルーチンフレームワークの助けを借りて行うことができます。
function* main() {
var result = yield loadFromDB('query')
}
これで、変数や物がロードされるのを待つ必要があるときに、必要に応じてメイン関数が生成されます。しかし今、これを実行するには、通常の(非コルーチン関数)を呼び出す必要があります。単純なコルーチンフレームワークでこの問題を修正できるため、次のコマンドを実行するだけで済みます。
start(main())
そして、開始が定義されます(Nick Sotiroの回答から)
function start(routine, data) {
result = routine.next(data);
if(!result.done) {
result.value(function(err, data) {
if(err) routine.throw(err); // continue next iteration of routine with an exception
else start(routine, data); // continue next iteration of routine normally
});
}
}
そして今、あなたははるかに読みやすく、削除しやすく、インデントや関数などをいじる必要のない美しいコードを持つことができます。
興味深い観察結果は、この例でyield
は、実際には、コールバックを使用して関数の前に配置できる単なるキーワードであるということです。
function* main() {
console.log(yield function(cb) { cb(null, "Hello World") })
}
「HelloWorld」を印刷します。したがって、同じ関数シグネチャを(cbなしで)作成し、次のようyield
に返すだけで、実際に任意のコールバック関数を使用に変えることができます。function (cb) {}
function yieldAsyncFunc(arg1, arg2) {
return function (cb) {
realAsyncFunc(arg1, arg2, cb)
}
}
うまくいけば、この知識があれば、削除しやすい、よりクリーンで読みやすいコードを書くことができます!
完全な答えを与えるために:yield
は、と同様に機能しreturn
ますが、ジェネレーターで動作します。
一般的に与えられる例に関しては、これは次のように機能します。
function *squareGen(x) {
var i;
for (i = 0; i < x; i++) {
yield i*i;
}
}
var gen = squareGen(3);
console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4
しかし、yieldキーワードの2番目の目的もあります。ジェネレータに値を送信するために使用できます。
明確にするために、小さな例:
function *sendStuff() {
y = yield (0);
yield y*y;
}
var gen = sendStuff();
console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4
これは、値2
がに割り当てられてy
いるため、最初のyield(戻り値0
)で停止した後、ジェネレーターに送信することで機能します。
これにより、本当にファンキーなものを作ることができます。(コルーチンを検索)
Yield
javaScript関数のキーワードはそれをジェネレータにします、
JavaScriptのジェネレーターとは何ですか?
ジェネレーターは、単一の値ではなく一連の結果を生成する関数です。つまり、一連の値を生成します。
ジェネレーターは、ヘルプイテレーターと非同期で作業するのに役立ちます。ハックイテレーターとは何ですか?本当?
イテレータとは、一度に1つずつアイテムにアクセスできることを意味します。
イテレータは、アイテムに一度に1つずつアクセスするのに役立ちますか?ジェネレーター関数を介してアイテムにアクセスするのに役立ちます。ジェネレーター関数は、yield
キーワードを使用する関数であり、yieldキーワードは、関数の実行を一時停止および再開するのに役立ちます。
簡単な例を次に示します。
function *getMeDrink() {
let question1 = yield 'soda or beer'; // execution will pause here because of yield
if (question1 == 'soda') {
return 'here you get your soda';
}
if (question1 == 'beer') {
let question2 = yield 'What\'s your age'; // execution will pause here because of yield
if (question2 > 18) {
return "ok you are eligible for it";
} else {
return "Shhhh!!!!";
}
}
}
let _getMeDrink = getMeDrink(); // initialize it
_getMeDrink.next().value; // "soda or beer"
_getMeDrink.next('beer').value; // "What's your age"
_getMeDrink.next('20').value; // "ok you are eligible for it"
_getMeDrink.next().value; // undefined
何が起こっているのか簡単に説明しましょう
yield
各キーワードで実行が一時停止されていることに気づきましたyield
。イテレータを使用して最初にアクセスできます。.next()
これは一度にすべてのキーワードを繰り返し、簡単な単語でキーワードが残ってyield
いない場合は未定義を返します。キーワードはブレークポイントであり、関数は毎回一時停止し、この場合はイテレータを使用して呼び出すと再開します。これは例です。関数の各ブレークポイントにアクセスするのに役立つイテレータのyield
yield
_getMeDrink.next()
ジェネレーターの例:
async/await
あなたがあなたの実装が仕事をするために使われるのasync/await
を見るならば、どんな提案も歓迎されることを指摘してください。generator functions & promises
async/await
イテレータジェネレータに使用されます。基本的に、手続き型コードを使用して(潜在的に無限の)シーケンスを作成できます。Mozillaのドキュメントを参照してください。
yield
コルーチンフレームワークを使用して、コールバック地獄を排除するために使用することもできます。
function start(routine, data) {
result = routine.next(data);
if(!result.done) {
result.value(function(err, data) {
if(err) routine.throw(err); // continue next iteration of routine with an exception
else start(routine, data); // continue next iteration of routine normally
});
}
}
// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}
function* routine() {
text = yield read('/path/to/some/file.txt');
console.log(text);
}
// with mdn javascript 1.7
http.get = function(url) {
return function(callback) {
// make xhr request object,
// use callback(null, resonseText) on status 200,
// or callback(responseText) on status 500
};
};
function* routine() {
text = yield http.get('/path/to/some/file.txt');
console.log(text);
}
// invoked as.., on both mdn and nodejs
start(routine());
イールドキーワードを使用したフィボナッチ数列ジェネレータ。
function* fibbonaci(){
var a = -1, b = 1, c;
while(1){
c = a + b;
a = b;
b = c;
yield c;
}
}
var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2
非同期JavaScript呼び出し間の依存関係。
歩留まりの使用方法のもう1つの良い例。
function request(url) {
axios.get(url).then((reponse) => {
it.next(response);
})
}
function* main() {
const result1 = yield request('http://some.api.com' );
const result2 = yield request('http://some.otherapi?id=' + result1.id );
console.log('Your response is: ' + result2.value);
}
var it = main();
it.next()
歩留まりについて学ぶ前に、発電機について知る必要があります。ジェネレーターは、function*
構文を使用して作成されます。ジェネレーター関数はコードを実行しませんが、代わりにジェネレーターと呼ばれるイテレーターのタイプを返します。メソッドを使用して値が指定されるnext
と、ジェネレーター関数は、yieldキーワードに到達するまで実行を続けます。を使用yield
すると、2つの値を含むオブジェクトが返されます。1つは値で、もう1つは実行されます(ブール値)。値は、配列、オブジェクトなどにすることができます。
簡単な例:
const strArr = ["red", "green", "blue", "black"];
const strGen = function*() {
for(let str of strArr) {
yield str;
}
};
let gen = strGen();
for (let i = 0; i < 5; i++) {
console.log(gen.next())
}
//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:
console.log(gen.next());
//prints: {value: undefined, done: true}
また、yieldキーワードを理解しようとしています。私の現在の理解に基づくと、ジェネレーターでは、yieldキーワードはCPUコンテキストスイッチのように機能します。イールドステートメントを実行すると、すべての状態(ローカル変数など)が保存されます。
これに加えて、{value:0、done:false}のように、直接の結果オブジェクトが呼び出し元に返されます。呼び出し元は、この結果オブジェクトを使用して、next()を呼び出すことによってジェネレーターを再度「ウェイクアップ」するかどうかを決定できます(next()を呼び出すと、実行が繰り返されます)。
もう1つの重要なことは、ローカル変数に値を設定できることです。この値は、ジェネレーターを「ウェイクアップ」するときに「next()」呼び出し元によって渡すことができます。たとえば、it.next('valueToPass')は、次のようになります。 "resultValue = yield slowQuery(1);" 次の実行をウェイクアップするときと同じように、呼び出し元は実行結果を実行に注入できます(ローカル変数に注入します)。したがって、この実行には2種類の状態があります。
最後の実行で保存されたコンテキスト。
この実行のトリガーによって注入された値。
したがって、この機能を使用すると、ジェネレーターは複数の非同期操作を分類できます。最初の非同期クエリの結果は、ローカル変数(上記の例ではresultValue)を設定することによって2番目のクエリに渡されます。2番目の非同期クエリは、最初の非同期クエリの応答によってのみトリガーできます。次に、ローカル変数は最初のクエリの応答から挿入された値であるため、2番目の非同期クエリはローカル変数の値をチェックして次のステップを決定できます。
非同期クエリの難しさは次のとおりです。
コールバック地獄
コールバックでパラメータとして渡さない限り、コンテキストが失われます。
イールドとジェネレーターは両方に役立ちます。
イールドとジェネレーターがない場合、複数の非同期クエリを分類するには、パラメーターをコンテキストとしてネストしたコールバックが必要であり、読み取りと保守が容易ではありません。
以下は、nodejsで実行される連鎖非同期クエリの例です。
const axios = require('axios');
function slowQuery(url) {
axios.get(url)
.then(function (response) {
it.next(1);
})
.catch(function (error) {
it.next(0);
})
}
function* myGen(i=0) {
let queryResult = 0;
console.log("query1", queryResult);
queryResult = yield slowQuery('https://google.com');
if(queryResult == 1) {
console.log("query2", queryResult);
//change it to the correct url and run again.
queryResult = yield slowQuery('https://1111111111google.com');
}
if(queryResult == 1) {
console.log("query3", queryResult);
queryResult = yield slowQuery('https://google.com');
} else {
console.log("query4", queryResult);
queryResult = yield slowQuery('https://google.com');
}
}
console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");
以下は実行結果です。
+++++++++++ start +++++++++++
query1 0
+++++++++++ end +++++++++++
query2 1
query4 0
以下の状態パターンは、上記の例と同様のことを行うことができます。
const axios = require('axios');
function slowQuery(url) {
axios.get(url)
.then(function (response) {
sm.next(1);
})
.catch(function (error) {
sm.next(0);
})
}
class StateMachine {
constructor () {
this.handler = handlerA;
this.next = (result = 1) => this.handler(this, result);
}
}
const handlerA = (sm, result) => {
const queryResult = result; //similar with generator injection
console.log("query1", queryResult);
slowQuery('https://google.com');
sm.handler = handlerB; //similar with yield;
};
const handlerB = (sm, result) => {
const queryResult = result; //similar with generator injection
if(queryResult == 1) {
console.log("query2", queryResult);
slowQuery('https://1111111111google.com');
}
sm.handler = handlerC; //similar with yield;
};
const handlerC = (sm, result) => {
const queryResult = result; //similar with generator injection;
if (result == 1 ) {
console.log("query3", queryResult);
slowQuery('https://google.com');
} else {
console.log("query4", queryResult);
slowQuery('https://google.com');
}
sm.handler = handlerEnd; //similar with yield;
};
const handlerEnd = (sm, result) => {};
console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");
実行結果は次のとおりです。
+++++++++++ start +++++++++++
query1 0
+++++++++++ end +++++++++++
query2 1
query4 0
ジェネレーターをループするのに非常に役立つ「xofgenerator」構文を忘れないでください。next()関数を使用する必要はまったくありません。
function* square(x){
for(i=0;i<100;i++){
x = x * 2;
yield x;
}
}
var gen = square(2);
for(x of gen){
console.log(x);
}