JavaScriptのyieldキーワードは何ですか?


237

JavaScriptで「yield」キーワードについて聞いたことがありますが、それについてのドキュメントは非常に貧弱です。誰かがその使用法とその使用目的を説明してくれますか(または説明するサイトを勧めます)。


彼はおそらく「利回り」を意味するbytes.com/topic/python/answers/685510-yield-keyword-usage
ant

4
MDNで説明されていますが、これはFirefoxでのみ機能すると思いますよね?それはどれくらいポータブルですか?これをChromeまたはnode.jsで行う方法はありますか?PD:申し訳ありませんが、それはJavascript v1.7 +です。そのため、サポートを探すときに参照するプロパティです。
Trylks

1
@Trylks:v0.11.2以降、Nodeでジェネレーターが利用可能
Janus Troelsen

@JanusTroelsenただし、フラグの後ろのみ。それらはioJSでネイティブにサポートされています
Dan Pantry

回答:


85

MDNドキュメントは IMO、かなり良いです。

yieldキーワードを含む関数はジェネレーターです。これを呼び出すと、その仮パラメーターは実際の引数にバインドされますが、本体は実際には評価されません。代わりに、ジェネレーターイテレーターが返されます。ジェネレーター/イテレーターのnext()メソッドを呼び出すたびに、反復アルゴリズムを通じて別のパスが実行されます。各ステップの値は、yieldキーワードで指定された値です。yieldは、アルゴリズムの各反復間の境界を示すreturnのジェネレーターイテレーターバージョンと考えてください。next()を呼び出すたびに、生成コードは、yieldに続くステートメントから再開します。


2
@NicolasBarbulescoをクリックしてMDNドキュメントに移動すると、非常に明らかに配置された例があります。
マットボール

@MattBall-このようなPIのJavaScriptとしての関数は、次のように十分でしょう:function * PI {PI =((Math.SQRT8;)/ 9801;); }-または、このPIの計算のためにJavaScriptに実装された関数はすでにありますか?
dschinn1001 2015

4
ここでMDNを引用する意味は何ですか?誰もがMDNでそれを読むことができると思います。訪問はdavidwalsh.name/promisesそれらについての詳細を学ぶために。
Ejaz Karim 2017

20
(a)質問者がそれを呼び出したときに「非常に貧弱なドキュメント」のコピーであり、(b)何も役に立たないと言っている場合、これはどのようにして最大80票を獲得しましたか?以下のはるかに良い答え。
www-0av-Com 2017

4
説明を求められた場合、ドキュメントをコピーして貼り付けるだけではまったく役に立ちません。質問とは、ドキュメントで既に検索したが、それらを理解していなかったことを意味します。
ディエゴ

205

遅ればせながら、おそらく誰もがyield今は知っていますが、いくつかのより良いドキュメントがやって来ました。

James Longによる"Javascript's Future:Generators"の例を公式のHarmony標準に適合させます。

function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

「fooを呼び出すと、次のメソッドを持つジェネレーターオブジェクトが返されます。」

var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

だから、yield一種のようであるreturn:あなたが何かを取り戻します。 return x値を返しますxが、yield x次の値に向かって反復するためにあなたの方法を与える関数を返します。反復中に中断する可能性のある、メモリを大量に消費する可能性のある手順がある場合に役立ちます。


13
参考、私はそのあなたを推測するfunction* foo(x){
ラナディープ

9
@RanaDeep:関数構文が拡張され、オプションの *トークンが追加されます。必要かどうかは、戻ってくる将来の種類によって異なります。詳細は長いです:GvRは、Javascript実装がモデル化されているPython実装について説明しています。使用すると、function *いくつかのケースではわずかに多くのオーバーヘッドよりも関わらず、常に、右のだろうfunctionyield
ビショップ

1
@ Ajedi32はい、そうです。調和の間の相関を標準function *yieldし、引用エラー(「収量又は収率*発現は、非ジェネレータ関数で発生した場合に早期にエラーが発生した」)を添加しました。ただし、Firefoxの元のJavascript 1.7実装では、は必要ありませんでした*。それに応じて回答を更新しました。ありがとう!
ビショップ

3
@MuhammadUmer Jsがようやく実際に使える言語になりました。それは進化と呼ばれています。
Lukas Liesis、2016年

1
例は便利ですが、関数とは何ですか*?
ディエゴ

65

それは本当にシンプルです、これはそれがどのように機能するかです

  • 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キーワードまで関数を再開するには、_process.next()を呼び出す必要があります。

複数のyieldは、単一の関数内のJavaScriptデバッガのブレークポイントであると考えることができます。次のブレークポイントをナビゲートするように指示するまで、コードブロックは実行されません。(:アプリケーション全体をブロックせずに)

ただし、yieldがこの一時停止と再開の動作を実行している間、結果返すこともできますが{value: any, done: boolean} 、前の関数では値を出力していません。以前の出力を調べると、undefinedの{ value: undefined, done: false } 値で同じことが表示されます。

yieldキーワードを詳しく見てみましょう。オプションで、を追加し、デフォルトのオプション値を割り当てるように設定できます。(公式ドキュメント構文)

[rv] = yield [expression];

expression:ジェネレーター関数から返す値

yield any;
yield {age: 12};

rv:ジェネレータのnext()メソッドに渡されたオプションの値を返します

このメカニズムを使用してパラメーターをprocess()関数に渡すだけで、さまざまな歩留りパーツを実行できます。

let val = yield 99; 

_process.next(10);
now the val will be 10 

やってみよう

使い方

  • 遅延評価
  • 無限シーケンス
  • 非同期制御フロー

参照:


54

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

一方、ではyield、これらすべてを1行で実行できます。これには、優れたコルーチンフレームワークが役立ちます。

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") })
}

「Hello World」と印刷します。したがって、次のようにyield、同じ関数シグネチャ(cbなし)を作成してを返すだけで、実際にコールバック関数を使用することができますfunction (cb) {}

function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

うまくいけば、この知識があれば、削除簡単な、よりクリーンで読みやすいコードを記述できます。


a function*は利回りのない通常の関数ですか?
Abdul

function *それ利回りを含む関数だと思います。これはジェネレーターと呼ばれる特別な関数です。
Leander

7
すでにyieldどこでも使用している人にとって、これはコールバックよりも理にかなっていると確信していますが、これがコールバックよりも読みやすいかどうかはわかりません。
palswim 2016年

その記事は理解しにくい
Martian2049

18

完全な答えを与えるために: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、最初の歩留まり(が返された0)で停止した後、ジェネレーターに送信することによって機能します。

これにより、本当にファンキーなものをいくつか作成することができます。(コルーチンを検索)



6

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());

4

yieldキーワードを使用したフィボナッチ数列ジェネレーター。

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 

4

Yeild javaScript関数のキーワードはそれをジェネレーターにします。

javaScriptのジェネレーターとは

ジェネレータは、単一の値ではなく一連の結果を生成する関数です。つまり、一連の値を生成します

つまり、ジェネレーターはヘルプイテレーターと非同期で動作するのに役立ちます。本当に?

イテレータとは、アイテムに一度に1つずつアクセスできるという意味です

イテレータが一度に1つのアイテムにアクセスするのに役立つ場所 ジェネレータ関数を介してアイテムにアクセスするのに役立ちます

ジェネレータ関数はyeild、キーワードを使用する関数であり、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 'Whats 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  // "Whats your age"

_getMeDrink.next('20').value  // "ok you are eligible for it"

_getMeDrink.next().value // undefined

何が起こっているのか簡単に説明しましょう

yeildキーワードで実行が一時停止されていることに気付きました。yieldイテレータの助けを借りて、最初にアクセスできます。.next()

これyieldは、一度にすべてのキーワードに対して反復しyield、単純な単語でキーワードが残っていない場合はundefinedを返します。yieldキーワードは、関数が一時停止するたびにブレークポイントであり、イテレータを使用して呼び出すときにのみ再開するブレークポイントます

私たちの場合:_getMeDrink.next()これは、関数の各ブレークポイントにアクセスするのに役立つイテレータの例です

ジェネレーターの例: async/await

async/await あなたが実装を見ると、あなたは仕事generator functions & promisesをするのに使われてasync/awaitいるのを見るでしょう

提案は歓迎されていることを指摘してください


3

非同期JavaScript呼び出し間の依存関係。

歩留まりを使用できる別の良い例。

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()


0

イールドについて学ぶ前に、ジェネレータについて知る必要があります。ジェネレーターはfunction*構文を使用して作成されます。ジェネレーター関数はコードを実行せず、代わりにジェネレーターと呼ばれるタイプのイテレーターを返します。nextメソッドを使用して値が指定されると、ジェネレーター関数は、yieldキーワードが見つかるまで実行を続けます。を使用yieldすると、2つの値を含むオブジェクトが返されます。1つは値で、もう1つは実行されます(ブール値)。値は、配列、オブジェクトなどにすることができます。


0

簡単な例:

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}

0

また、yieldキーワードを理解しようとしています。私の現在の理解に基づいて、ジェネレーターでは、yieldキーワードはCPUコンテキストスイッチのように機能します。yieldステートメントを実行すると、すべての状態(ローカル変数など)が保存されます。

これに加えて、{value:0、done:false}のような直接の結果オブジェクトが呼び出し元に返されます。呼び出し元は、この結果オブジェクトを使用して、next()を呼び出すことでジェネレータを再び「ウェイクアップ」するかどうかを決定できます(next()を呼び出すことは実行を繰り返すことです)。

もう1つの重要なことは、ローカル変数に値を設定できることです。この値は、ジェネレーターを「ウェイクアップ」するときに「next()」呼び出し元から渡すことができます。たとえば、次のようにit.next( 'valueToPass'): "resultValue = yield slowQuery(1);" 次の実行を起こすときと同様に、呼び出し側は実行結果を実行に注入できます(ローカル変数に注入します)。したがって、この実行には2種類の状態があります。

  1. 最後の実行で保存されたコンテキスト。

  2. この実行のトリガーによって注入された値。

したがって、この機能により、ジェネレーターは複数の非同期操作を整理できます。最初の非同期クエリの結果は、ローカル変数(上記の例ではresultValue)を設定することにより、2番目のクエリに渡されます。2番目の非同期クエリは、最初の非同期クエリの応答によってのみトリガーできます。次に、ローカル変数は最初のクエリの応答から挿入された値であるため、2番目の非同期クエリはローカル変数値をチェックして次の手順を決定できます。

非同期クエリの難点は次のとおりです。

  1. コールバック地獄

  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


0

ジェネレーターをループするための非常に便利な「ジェネレーターのx」構文を忘れないでください。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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.