JS非同期プログラミングのいくつかの方法と違い

13042 ワード

前言
周知のように、Javascriptは「単一スレッド」言語であり、実際の開発においては、非同期論理の処理に直面しなければならない.非同期とは、一つのタスクを実行するには、AとBの二つの段階に分けられ、A段階を実行した後、もう一つのタスクを実行して結果を得てからB段階を実行するということです.非同期プログラミングには、callbackPromiseGeneratorasync (callback hell)のいくつかの一般的な方法があります.
カルバック関数
calback関数とは、Promiseを介して他の実行コードに伝達された、あるブロックで実行可能なコードの参照を指し、メイン関数によって呼び出された後、メイン関数に戻ります.
function add(a, b, callback){
    var num = a + b;
    callback(num)
}
add(1, 2, function(num){
    console.log(num); # 3
    # ...
})
タスクのキューがある場合は、複数のタスクが含まれています.
var readFile = require('fs-readfile-promise'); #       
readFile(fileA, function(data) {
    readFile(fileB, function(data) {
        # ...
    })
})
上述のように、n個のタスクが存在すると、n層を積層する必要があり、このコードは非常に冗長で結合度が高く、その中のある関数を修正すると、上下関数のコードブロックの論理に影響します.この場合は「コールバック地獄」と呼ばれています.
Promise
Promiseは私たちがよく使っている非同期の問題を解決する方法です.コールバック関数のネストをチェーン呼び出しに変更できます.以上の複数のタスクは、次の例に変換できます.
function add(a, b){
    return new Promise((resolve, reject) => {
        var result = a+b;
        resolve(result);
    })
}
add(10, 20).then(res => {
    return add(res, 20) # res = 30
}).then(res => {
    return add(res, 20) # res = 50
}).then(res => {
    // ...
}).catch(err => {
    //     
})
add関数が実行されるとPromiseに戻り、その結果はthen方法に入り、第一のパラメータはresolvePromiseの結果、第二のパラメータ(オプション)はrejectthenの結果となる.このようなチェーン方式の書き方は、各イベントのコールバック処理を効果的に分割し、コード構造がより明確になるように、コールバックされたロジックをcatch方法で書くことができる.また、Promiseでエラーを処理できます.
私達の非同期の要求が順序A->>B->C->Dではなく、[A,B,C]->Dであれば、まずA、B、Cを並行して実行してからDを実行します.
#     Promise     
const promises = [2, 3, 5].map(function (id) {
  return getJSON('/post/' + id + ".json"); # getJSON     Promise         
});

Promise.all(promises).then(function (posts) {
  # promises      Promise
  # posts        ,    Promise     
  #       D  
}).then(res => {
    //...
}).catch(function(reason){
    //...
});
しかし、Promiseのコードには、new Promiseによって包装された関数のような余分なコードがあります.thencatchnextがあります.
Generator関数
Generator関数はES 6によって提供される非同期プログラミングソリューションであり、関数を実行するごとにリターンされるのはエルゴードオブジェクトであり、戻るオブジェクトはGeneratorのそれぞれの状態を順次巡回することができ、エルゴードオブジェクトのfunction方法で関数を実行する必要がある.
まず例を示します
function* foo() {
    yield 'stepone';
    yield 'steptwo';
    return 'stepthree';
}
var _foo = foo();
_foo.next(); #{value: 'stepone', done: false}
_foo.next(); #{value: 'stepone', done: false}
_foo.next(); #{value: 'stepone', done: false}
Generatorには3つの特徴があります.関数名は*の後にyieldを追加する必要があります.関数の内部にnextがあります.外部実行にはreturnメソッドを呼び出す必要があります.各yieldは、彼女の後に付いている値をオブジェクトの戻りとして包み、返したオブジェクトには、doneに戻り、trueGeneratorに戻るまで、戻り値と関数の実行状態が含まれる.
Generator関数を実行するたびにnextを使う必要があります.あなたは面倒くさいです.自動実行器が必要です.coモジュールは有名なプログラマTJ Holowaychukが2013年6月に発表したツールで、Generator関数の自動実行に使用されます.coモジュールを使用する場合、yieldの後ろにはThunk関数またはPromiseオブジェクトしかありません.co関数の実行が完了したら戻ってくるのはPromiseです.以下のとおりです
var co = require('co');
var gen = function* () {
  var img1 = yield getImage('/image01');
  var img2 = yield getImage('/image02');
  ...
};
co(gen).then(function (res){
  console.log(res);
}).catch(err){
    #     
};
coモジュールのタスクの並列処理は、複数のタスクを並列に実行してから次のステップに進みます.
#      
co(function* () {
  var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(res);
}).then(console.log).catch(onerror);

#      
co(function* () {
  var res = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2),
  };
  console.log(res);
}).then(console.log).catch(onerror);
Promise関数は、書き方においてcoよりも簡潔で論理的に明確であるが、この最適化を解決するために、async関数が追加的に実行される必要がある.
async関数
async関数はGenerator関数のシンタックス飴です.
var co = require('co');
var gen = function* () {
  var img1 = yield getImage('/image01');
  var img2 = yield getImage('/image02');
  ...
};
co(gen).then(function (res){
  console.log(res);
}).catch(err){
    #     
};
****
#  Generator      
var gen = async function () {
  var img1 = await getImage('/image01');
  var img2 = await getImage('/image02');
  return [img1, img2];
  ...
};
gen().then(res => {
    console.log(res) # [img1, img2]
});
Generator関数と比較して、asyncの関数の書き方の違いはasyncの代わりに*awaityieldの代わりになっています.asyncはアクチュエータを備えています.より良い適応性を持ち、awaitの後にPromiseであっても良いし、元のタイプの値であっても良い.また、async関数はPromiseを返しています.私たちのより良い処理の戻り値になります.
async function gen() {
  return '111';
  #     return await '111';
};
gen().then(res => {
    console.log(res) # 111
});
直接return値であれば、この値は自動的にthenメソッドのコールバック関数の値になります.
async function gen() {
  var a = await getA();
  var b = await getB();
  return a + b;
};
gen().then(res => {
    console.log(res)
});
async関数によって返されたPromiseは、関数の体内のすべてのawaitの後のPromiseオブジェクトが実行された後、またはreturnまたは の後に状態が変化する必要がある.つまり、async内の非同期動作が全部終了してこそ、メインタスクに戻り、then方法では、メインタスクを継続して実行することができるということです.
#     1
async function gen() {
    await new Promise((resolve, reject) => {
        throw new Error('   ');
    })
};
gen().then(res => {
    console.log(res)
}).catch(err => {
    console.log(err) #    
});
#     2:    ,  await            await     
async function gen() {
    try{
        await new Promise((resolve, reject) => {
            throw new Error('   ');
        })
    }catch(e){
        console.log(e); #    
    }
    return Promise.resolve(1);
};
gen().then(res => {
    console.log(res) # 1
});
エラー処理は上記の通りです.
async function gen() {
    #    
    let result = await Promise.all([getName(), getAddress()]);
    return result;
    #    
    let namePromise = getName();
    let addressPromise = getAddress();
    let name = await namePromise;
    let address = await addressPromise;
    return [name, address];
};
gen().then(res => {
    console.log(res); #     ,   getName getAddress   
})
複数の非同期タスクは互いに依存関係がなく、併発が必要な場合は、上記の2つの方法で書くことができます.
asyncとPromise、Generator関数の比較
function chainAnimationsPromise(elem, animations) {
  #   ret             
  let ret = null;
  #       Promise
  let p = Promise.resolve();
  #   then  ,      
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }
  #               Promise
  return p.catch(function(e) {
    #     
  }).then(function() {
    return ret;
  });

}
Promiseは地獄の逆転問題をよく解決しましたが、コードの中には意味に関係のないthencatchなどがたくさんあります.
function chainAnimationsGenerator(elem, animations) {
  return co(function*() {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      #     
    }
    return ret;
  });
}
Generator関数は、関数を実行するために自動アクチュエータを必要とし、yieldの後はPromiseオブジェクトまたはThunk関数だけである.
async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    #     
  }
  return ret;
}
async関数の実装は最も簡潔であり、最も意味に合致し、意味不関連コードはほとんどない.Generatorと比較して、プログラマがもう1つのアクチュエータを提供する必要がなく、async自体が自動的に実行し、簡潔に使用するのに便利である.
参考:ECMAScript 6阮一峰
転載先:https://juejin.im/post/5c95d9696fb9a070fa3769a9