ジェネレーターによるasync / awaitとES6のyieldの違い


82

この素晴らしい記事«ジェネレーター»を読んでいたところ、ジェネレーター関数を処理するためのヘルパー関数であるこの関数が明確に強調されています。

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

私が仮定するのは、多かれ少なかれ、asyncキーワードがasync/で実装される方法awaitです。それで問題は、もしそうなら、awaitキーワードとキーワードの違いは一体何なのかということyieldです。await常に何かを約束に変えますyieldが、そのような保証はしませんか?それが私の一番の推測です!

また、この記事では、async/awaityieldジェネレーターとどのように類似しているかを確認できます。この記事では、「スポーン」関数ES7非同期関数について説明しています


1
非同期関数->コルーチン。ジェネレーター->コルーチンを使用して内部反復メカニズムを管理するイテレーター。awaitはコルーチンを一時停止しますが、yieldは、一部のジェネレーターが使用するコルーチンからの結果を返します
David Haim

1
async/awaitES7の一部ではありません。タグの説明をお読みください。
Felix Kling 2016年

@david haim、ええ、でもasync awaitはジェネレーターの上に構築されているので、区別されません
Alexander Mills

回答:


46

yieldの構成要素と見なすことができますawaityield与えられた値を受け取り、それを呼び出し元に渡します。呼び出し元は、その値(1)を使用して必要な操作を実行できます。後で、呼び出し元はgenerator.next()yield式(2)の結果になる値、またはyield式(3)によってスローされたように見えるエラーをジェネレーターに(経由で)返す場合があります。

async-awaitを使用すると見なすことができますyield。(つまり、(1)、呼び出し元にasync-awaitドライバ-あなたが投稿機能と同様)と同様のアルゴリズム使用して約束に値をラップしますnew Promise(r => r(value)(ノート、ないし Promise.resolve、それは大したことではありません)。次に、約束が解決されるのを待ちます。満たされた場合、満たされた値を(2)に戻します。拒否した場合、(3)で拒否理由をエラーとしてスローします。

したがって、async-の効用は、生成された値をpromiseとしてアンラップし、解決された値を返し、関数が最終値を返すまで繰り返すためにawait使用yieldするこの機構です。


1
この議論と矛盾するこの答えstackoverflow.com/a/39384160/3933557を確認してください。async-awaitはyieldに似ていますが、内部でpromiseチェーンを使用します。「async-awaitはyieldを使用すると見なすことができます」という優れたリソースがある場合は共有してください。
サマレンドラ

1
それはこの答えと同じことを言っているので、あなたがその答えを「この議論と矛盾している」とどのように受け止めているのかわかりません。>それまでの間、Babelのようなトランスパイラーを使用すると、async / awaitを記述して、コードをジェネレーターに変換できます。
Arnavion

それは、babelがジェネレーターに変換すると言っていますが、あなたが言っているのは、「yieldはawaitの構成要素と見なすことができます」と「async-awaitはyieldを使用すると見なすことができます」です。これは私の理解には正しくありません(修正される可能性があります)。async-awaitは、その回答に記載されているように、内部でpromiseチェーンを使用します。私が欠けているものがあるかどうかを理解したいのですが、これについてあなたの考えを共有してください。
サマレンドラ

この答えは、全世界のすべてのESエンジンがジェネレーターを使用して内部的にpromiseを実装しているという主張をするものではありません。一部の人は; 一部はそうではないかもしれません。これが答えであるという質問とは無関係です。それにもかかわらず、約束が機能する方法は、発電機を駆動する特定の方法で発電機を使用して理解することができ、それがこの答えが説明していることです。
Arnavion

45

さて、async/awaitとジェネレータの間には非常に密接な関係があることがわかりました。そして私はasync/awaitは常にジェネレーター上に構築されると信じています。Babelが発生する方法を見るとasync/ await

バベルはこれを取ります:

this.it('is a test', async function () {

    const foo = await 3;
    const bar = await new Promise(resolve => resolve('7'));
    const baz = bar * foo;
    console.log(baz);

});

そしてそれをこれに変えます

function _asyncToGenerator(fn) {
    return function () {
        var gen = fn.apply(this, arguments);
        return new Promise(function (resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function (value) {
                        return step("next", value);
                    }, function (err) {
                        return step("throw", err);
                    });
                }
            }

            return step("next");
        });
    };
}


this.it('is a test', _asyncToGenerator(function* () {   // << now it's a generator

    const foo = yield 3;    //  <<< now it's yield, not await
    const bar = yield new Promise(resolve => resolve(7));
    const baz = bar * foo;
    console.log(baz);

}));

あなたは数学をします。

これにより、asyncキーワードはそのラッパー関数のように見えますが、その場合はawait、に変換されるだけでyield、後でネイティブになったときに画像にもう少し多くのことがあるでしょう。

あなたはここでこれについてのより多くの説明を見ることができます:https//www.promisejs.org/generators/


2
NodeJSには、ジェネレーターなしで、しばらくの間ネイティブの非同期/待機があります:codeforgeek.com/2017/02/…–
Bram

3
@Bramネイティブ実装は、完全に内部でジェネレーターを使用します。同じことですが、抽象化されています。
アレクサンダーミルズ

3
私はそうは思いません。Async / awaitは、V8エンジンにネイティブに実装されています。ES6機能、async / awaitがES7であるジェネレーター。これは、V8エンジン(ノードで使用される)の5.5リリースの一部でした:v8project.blogspot.nl/2016/10/v8-release-55.html。それはES6ジェネレータにtranspile ES7非同期/待つことも可能ですが、NodeJSの新しいバージョンでこれはもはや必要ではない、と非同期/のawaitのパフォーマンスはその後も優れた発電機のようだ:medium.com/@markherhold/...
ブラム

1
async / awaitはジェネレーターを使用してその処理を実行します
Alexander Mills

1
@AlexanderMills async / awaitが内部でジェネレーターを使用するという正当なリソースを共有していただけますか?この引数と矛盾するこのansstackoverflow.com/a/39384160/3933557を確認してください。Babelがジェネレーターを使用しているからといって、それが内部で同様に実装されているという意味ではないと思います。この上の任意の考え
Samarendra

28

awaitキーワードとキーワードの違いは一体何yieldですか?

awaitキーワードのみで使用されるasync functionが、Syieldキーワードのみ発電機で使用されるfunction*S。そして、それらも明らかに異なります。1つはプロミスを返し、もう1つはジェネレーターを返します。

await常に何かを約束に変えますyieldが、そのような保証はしませんか?

はい、待望の値awaitを要求Promise.resolveします。

yield ジェネレータの外部で値を生成するだけです。


マイナーな問題ですが、私の回答で述べたように、仕様ではPromise.resolve(以前は使用されていました)を使用せず、Promiseコンストラクターによってより正確に表されるPromiseCapability :: resolveを使用します。
Arnavion 2016年

@Arnavion:async / await仕様が直接使用するPromise.resolveのとまったく同じものnew PromiseCapability(%Promise%)を使用しますが、Promise.resolve理解したほうがよいと思いました。
ベルギ2016年

1
Promise.resolve非同期にはない余分な「IsPromise == true?その後、同じ値を返す」短絡があります。つまり、await pどこpで約束が解決さにすることを新たに約束を返しますp一方、Promise.resolve(p)戻ってくるしp
Arnavion 2016年

ああ、私はそれを見逃しました-これはただの中にPromise.castあり、一貫性の理由で非推奨になったと思いました。しかし、それは問題ではありません。とにかく、その約束は実際にはわかりません。
ベルギ2016年

2
var r = await p; console.log(r);:のようなものに変換する必要があるp.then(console.log);一方で、pとして作成される可能性があります:var p = new Promise(resolve => setTimeout(resolve, 1000, 42));言う、「待つことに間違っているので、呼び出し、それは完全に遠く離れ「のawait」式からいくつかの他のコードがその呼び出したPromise.resolve」Promise.resolve、変換されたので、await式はつまり、Promise.then(console.log)呼び出されて出力され42ます。
そしてDejaVu

16

tl; dr

使用async/await発電機を超える時間の99%。どうして?

  1. async/awaitプロミスチェーンの最も一般的なワークフローを直接置き換えて、コードを同期しているかのように宣言できるようにし、コードを大幅に簡素化します。

  2. ジェネレーターは、相互に依存し、最終的に「完了」状態になる一連の非同期操作を呼び出すユースケースを抽象化します。最も単純な例は、最終的に最後のセットを返す結果をページングすることですが、必要に応じてページを呼び出すだけで、すぐに連続して呼び出すことはありません。

  3. async/awaitは実際には、promiseの操作を容易にするためにジェネレーターの上に構築された抽象化です。

非同期/待機とジェネレーターの非常に詳細な説明を参照してください


5

私が理解していたawait/async約束を持って使用したこのテストプログラムを試してみてください。

プログラム#1:約束なしでそれは順番に実行されません

function functionA() {
    console.log('functionA called');
    setTimeout(function() {
        console.log('functionA timeout called');
        return 10;
    }, 15000);

}

function functionB(valueA) {
    console.log('functionB called');
    setTimeout(function() {
        console.log('functionB timeout called = ' + valueA);
        return 20 + valueA;
    }, 10000);
}

function functionC(valueA, valueB) {

    console.log('functionC called');
    setTimeout(function() {
        console.log('functionC timeout called = ' + valueA);
        return valueA + valueB;
    }, 10000);

}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

プログラム#2:約束付き

function functionA() {
    return new Promise((resolve, reject) => {
        console.log('functionA called');
        setTimeout(function() {
            console.log('functionA timeout called');
            // return 10;
            return resolve(10);
        }, 15000);
    });   
}

function functionB(valueA) {
    return new Promise((resolve, reject) => {
        console.log('functionB called');
        setTimeout(function() {
            console.log('functionB timeout called = ' + valueA);
            return resolve(20 + valueA);
        }, 10000);

    });
}

function functionC(valueA, valueB) {
    return new Promise((resolve, reject) => {
        console.log('functionC called');
        setTimeout(function() {
            console.log('functionC timeout called = ' + valueA);
            return resolve(valueA + valueB);
        }, 10000);

    });
}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

0

多くの点で、ジェネレーターはasync / awaitのスーパーセットです。現在、async / awaitには、最も人気のあるasync / awaitのようなジェネレータベースのlibであるcoよりもクリーンなスタックトレースがあります。ジェネレーターを使用して独自のasync / awaitのフレーバーを実装しyield、非プロミスの組み込みサポートやRxJSオブザーバブルでのビルドなどの新しい機能を追加できます。

つまり、ジェネレーターはより柔軟性を提供し、ジェネレーターベースのライブラリは一般により多くの機能を備えています。しかし、async / awaitは言語の中核部分であり、標準化されており、あなたの下で変更されることはありません。また、それを使用するためにライブラリは必要ありません。async / awaitとジェネレーターの違いについて詳しく説明したブログ投稿があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.