web-dev-qa-db-ja.com

JavaScript非同期プログラミング:約束とジェネレーター

プロミスとジェネレーターを使用すると、非同期コードを作成できます。これらのメカニズムの両方がECMAスクリプト6に導入されている理由がわかりません。いつプロミスを使用するのがベストで、いつジェネレーターを使用するのですか?

49
sribin

これらの2つの手法の間に反対はありません。それらは互いにうまく補完して共存します。 Promiseは、まだ利用できない非同期操作の結果を取得する機能を提供します。 運命のピラミッド 問題を解決します。代わりに:

function ourImportantFunction(callback) {
  //... some code 1
  task1(function(val1) {
    //... some code 2
    task2(val1, function(val2) {
      //... some code 3
      task3(val2, callback);
    });
  });
}

あなたは書ける:

function ourImportantFunction() {
  return Promise.resolve()
    .then(function() {
        //... some code 1
        return task1(val3)
    })
    .then(function(val2) {
        //... some code 2
        return task2(val2)
    })
    .then(function(val2) {
        //... some code 3
        return task3(val2);
    });
}

ourImportantFunction().then(callback);

ただし、promiseを使用しても、非同期方式でコードを作成する必要があります。常にコールバックを関数に渡す必要があります。非同期コードの記述は、同期よりもはるかに困難です。コードが巨大であるという約束でさえ、アルゴリズムを見ることは難しくなります(それは非常に主観的で、誰かがそれについて議論することができます。しかし、プログラマーの大多数にとっては本当だと思います)。したがって、非同期コードを同期方式で記述したいと思います。そこで発電機が私たちを助けに来ています。したがって、上記のコードの代わりに次のように記述できます。

var ourImportantFunction = spawn(function*() {
    //... some code 1
    var val1 = yield task1();
    //... some code 2
    var val2 = yield task2(val1);
    //... some code 3
    var val3 = yield task3(val2);

    return val3;
});

ourImportantFunction().then(callback);

最も単純なspawn実現は次のようになります:

function spawn(generator) {
  return function() {    
    var iter = generator.apply(this, arguments);

    return Promise.resolve().then(function onValue(lastValue){
      var result = iter.next(lastValue); 

      var done  = result.done;
      var value = result.value;

      if (done) return value; // generator done, resolve promise
      return Promise.resolve(value).then(onValue, iter.throw.bind(iter)); // repeat
    });
  };
}

ご覧のとおり、value(非同期関数task{N}の結果)は約束である必要があります。コールバックでこれを行うことはできません。

あとは、spawnテクニックを言語自体に実装するだけです。したがって、spawnasyncに、yieldawaitに置き換えて、 ES7 async/await に移行します。

var ourImportantFunction = async function() {
    //... some code 1
    var val1 = await task1();
    //... some code 2
    var val2 = await task2(val1);
    //... some code 3
    var val3 = await task3(val2);

    return val3;
}

このビデオ を見て、これと他の今後のテクニックを理解することをお勧めします。男があまりにも速く話している場合は、再生速度を遅くします(右下隅の「設定」、または単に[shift + <]を押します)

最高のもの:コールバック、約束、またはジェネレーターによる約束-これは非常に主観的な質問です。コールバックは、現時点で可能な最速のソリューションです(ネイティブプロミスのパフォーマンスは現在非常に悪いです)。ジェネレーターを使用すると、非同期コードを同期式で作成できます。しかし今のところ、彼らは単純なコールバックよりもずっと遅い。

91
alexpods

PromiseとGeneratorは異なるソフトウェアパターン(構築)です。

  1. http://en.wikipedia.org/wiki/Futures_and_promises
  2. http://en.wikipedia.org/wiki/Generator_(computer_programming)

実際、ジェネレーターは非同期ではありません。

ジェネレータは、一連の値を一度に取得する必要はなく、需要ごとに1つ取得する必要がある場合に役立ちます。ジェネレータは、シーケンスの最後に到達するまで(または無限のシリーズの場合は無限に)、すべての呼び出しで次の値を直ちに(同期的に)返します。

Promiseは、値を「延期」する必要がある場合に役立ちます。値をまだ計算できない(または利用できない)場合があります。値が使用可能な場合-配列またはその他の複雑な値であっても、値全体(値の一部ではない)です。

ウィキペディアの記事で詳細と例を見ることができます。

10
Ivan Samygin