Javascript非同期プログラミング:Callback、Promise、Generator


同期と非同期(Synchronous and Aynchronous)
javascriptを知っている学生は同期と非同期の概念をよく知っているはずです.もしまだ知らない学生がいたら、イメージの例を挙げます.例えば、私達は朝起きたら3つのことをします.お湯を沸かし、顔を洗ってから朝食を食べます.一つの仕事が終わったら、もう一つの仕事をやります.私たちはお湯を沸かしながら朝食を食べます.(顔を洗わないで朝食を食べるのは衛生的ではないです.)朝食を食べてから顔を洗います.同期よりも非同期の方が効率的で、待ち時間が少なくなりました.同期プロセスの実行時間はすべての挙動の合計に依存します.非同期プロセスの実行時間は一番長いその挙動にのみ依存します.
Javascriptはシングルスレッドなので、同時に一つしか処理できません.上記の例ではこのスレッドは「私」です.例えば私は同時に顔を洗ったり朝食を食べたりすることができません.だから、実行効率を向上させるために、私たちはできるだけこのスレッドをアイドル状態ではなく、アイドル状態にしてください.私たちは水を沸かすなどしなくてもいいです.同時に他のことをすることができます.お湯を沸かすのはシステムの他のスレッドによって処理されます.コンピュータの世界では、多くのI/O密集型の操作が待っています.例えば、ネットワーク要求やファイルの読み書きなどです.
非同期プロセス制御
非同期の意味を理解した後、現在主流のいくつかの非同期プロセス制御方法を比較して、非同期プログラミングの最適な実践を検討します.
1.Callback
落とし穴をする
まずcalbackと非同期は必然的に関係していません.calbackは本質的にタイプがfunctionの関数パラメータです.このcalbackは同期ですか?それとも非同期ですか?関数自体によって異なります.calbackは非同期法のコールバックとしてよく使われているが、多くの同期方法は、最も一般的な配列のforEach方法のように、calbackにも伝わることができる.
var arr = [1, 2, 3];
arr.forEach(function (val) {
  console.log(val);
});
console.log('finish');

//     :1,2,3,finish
同様に、配列のmapfilterreduceなどの多くの方法がある.
非同期Callback
一般的な非同期のcalbackは、setTimeoutのようにリピートされる:
setTimeout(function () {
  console.log("time's up");
}, 1000);
console.log('finish');

//     :finish, time's up
遅延時間を0に変更すれば、非同期のcalback会などの関数の同期方法が全部完了してから実行されるので、印刷結果は相変わらずfinish, time's upになります.
Callback Hell
実際のプロジェクトではよくこのような問題があります.次の操作は前の操作の結果に依存しています.前の操作は前のステップの操作に依存しています.このようにして階段が多くなると、多くの層のcalbackネストが形成され、コードの可読性とメンテナンス性が悪くなり、いわゆるCallback Hellが形成される.
step1(param, function (result1) {
  step2(result1, function (result2) {
    step3(result2, function (result3) {
      step4(result3, function (result4) {
        done(result4);
      })
    })
  })
})
もちろん、calbackの使用を諦めない前提で、上のコードはやはり最適化されたスペースがあります.
step1(param, callbac1);

function callback1(result1){
  step2(result1, callback2);
}

function callback2(result2){
  step3(result2, callback3);
}

function callback3(result3){
  step4(result3, callback4);
}

function callback4(result4){
  done(result4);
}
Callback Hellの横方向の深さをコードの縦方向の高さに変換することに相当して、私達の慣習の上から下までの同期によって呼び出されるようになります.複雑さは変わっていません.ただもっとはっきり見えるだけです.欠点は余分な関数、変数を定義することです.この考えをさらに伸ばしたら次のPromiseがあります.
2.Promise
Promise中国語訳は「承諾」となり、Javascriptでは抽象的な概念であり、現在は実現されていないが、未来のある時点で実現することができる.例を挙げると、朝にお湯を沸かすという約束をしますが、10分後にはお湯が沸かせます.正常であれば、10分後にはお湯が沸騰します.このプロミスを代表してこのプロミを現金化しましたが、途中で停電したら、10分でお湯が沸かなくなりました.このプロミスは失敗しました.コードで表してもいいです.
const boilWaterInTenMins = new Promise(function (resolve, reject) {
  boiler.work(function (timeSpent) {
    if (timeSpent <= 10) {
      resolve();
    } else {
      reject();
    }
  });
});
互換性
ブラウザのPromiseへの互換性を向上させるためには、babelまたは第三者の実装を使用することができます.
Promise Chining
Promiseは非同期プロセスのコントロールに対してどのような向上がありますか?また上のCallback Hellの例に基づいてPromiseで実現すればどうなりますか?
まず、step1~doneの関数をPromiseで実現し、一連のチェーンコールをすればいいです.
stepOne(param)
  .then((result1) => { return step2(result1) })
  .then((result2) => { return step3(result2) })
  .then((result3) => { return step4(result3) })
  .then((result4) => { return done(result4) })
  .catch(err => handleError(err));
簡単ではないですか?
Async/Await
Promiseの呼び方に慣れていないなら、async/awaitで同期に近い呼び方に変えられます.
async function main() {
  try {
    var result1 = await step1(param);
    var result2 = await step2(result1);
    var result3 = await step3(result2);
    var result4 = await step4(result3);
    done(result4);
  } catch (err) {
    handleError(err);
  }
}

main();
3.Generator
Generatorはより抽象的な概念であり、Generatorとは何かを知るためにはまず他のいくつかの概念Iterable Protocol、Iteratocol、およびIterator Protocolを理解する必要があります.
Iterable Protocol
Iterable Protocolの特徴は要約できます.
  • は、javascriptオブジェクトを定義するための反復動作
  • を実行する.
  • オブジェクト自体またはプロトタイプチェーンにSymbol.iteratorという方法が必要です.
  • この方法は、任意のパラメータを受信せず、Iterator
  • を返す.
  • Iterableのオブジェクトはfor...ofを使用して
  • を巡回することができます.
    Javascript ArayはIterable Protocolを実現しました.従来の採値方式以外に、arrayのSymbol.iteratorも利用できます.
    var arr = [1, 2, 3];
    var iterator = arr[Symbol.iterator]();
    iterator.next(); // {value: 1, done: false}
    また、2倍の値を返すようなArayのデフォルトの反復方式を変更することができる.
    Array.prototype[Symbol.iterator] = function () {
      var nextIndex = 0;
      var self = this;
      return {
        next: function () {
          return nextIndex < self.length ?
            { value: self[nextIndex++] * 2, done: false } :
            { done: true }
        }
      };
    }
    
    for(let el of [1, 2, 3]){
      console.log(el);
    }
    //   :2,4,6
    
    Iterator Protocol
    Iterator Protocolの特徴は要約できます.
  • は、1つのシーケンス値(限定または無限)を生成する標準的な方法
  • である.
  • next方法を実現する
  • next方法で返される対象は{value: any, done: boolean}
  • である.
  • valueは戻り値であり、donetrueである場合valueは、
  • を省略することができる.
  • donetrueであり、反復終了を表し、valueは最終的な戻り値
  • を示す.
  • donefalseであり、次の値
  • を生成する反復を継続することができる.
    Iterator
    明らかにIteratorはIteratocolを実現した対象です.
    Generator
    上のいくつかの概念を理解した後、Generatorを理解すると簡単に多くなります.
  • は、Iterable ProtocolとIterator Protocolを同時に実現するので、Genratorは、一つのiterableのオブジェクトであり、もう一つのiterator
  • である.
  • Generatorは、generator functionから
  • を生成する.
    最も簡単なgenerator functionは、例えば:
    function* gen() {
      var x = yield 5 + 6;
    }
    
    var myGen = gen(); // myGen     generator
    next法を呼び出してyield式の値を得ることができます.
    myGen.next(); // { value: 11, done: false }
    しかし、xは、この時点では与えられていません.javascriptがyield 5 + 6を実行した後に停止したと考えられています.これを実行するためには、nextを再起動し、得られた値をフィードバックする必要があります.
    function* gen() {
      var x = yield 5 + 6;
      console.log(x); // 11
    }
    
    var myGen = gen();
    console.log(myGen.next()); // { value: 11, done: false }
    console.log(myGen.next(11)); // { value: undefined, done: true }
    これだけ言ったら、ゲナートと異歩はいったい何の関係がありますか?Promise+Generatorが実現した非同期制御(step 1~doneがPromiseに戻る)を見に来ました.
    genWrap(function* () {
      var result1 = yield step1(param);
      var result2 = yield step2(result1);
      var result3 = yield step3(result2);
      var result4 = yield step4(result3);
      var result5 = yield done(result4);
    });
    
    function genWrap(genFunc) {
      var generator = genFunc();
    
      function handle(yielded) {
        if (!yielded.done) {
          yielded.value.then(function (result) {
            return handle(generator.next(result));
          });
        }
      }
    
      return handle(generator.next());
    }
    async/awaitと同様に、このような実現も非同期の表記に転化していますが、これはES 7のasync/awaitの実現原理です.
    おわりに
    この文章は皆さんの助けになりたいです.javascriptの非同期プログラムをより深く理解して、より優雅で効率的なコードを書くことができます.間違いがあったら指摘してください.明けましておめでとうございます