ほとんどの JavaScript 開発者にとって、非同期関数は新しいものであり、その開発には長い道のりを経てきました。そこで、この記事では、JavaScript の非同期 関数の開発の歴史を整理してまとめてみます。少し前までは、非同期を実現するにはコールバック関数しか記述できませんでしたが、その後、Promise/A+ 標準が登場しました。その後、ジェネレーター関数が登場しました。 、そして将来は明らかに非同期関数です。
ここで、長年にわたる JavaScript 非同期関数の開発を振り返ってみましょう。
すべてはコールバック関数から開始する必要があるようです。
JavaScript で知られているように、非同期プログラミング方法は JavaScript 言語の第一級市民関数を通じてのみ実行できます。この方法は、ある関数を別の関数として使用できることを意味します。パラメーター、渡された関数 (つまり、コールバック) function) はこの関数内で呼び出すことができます。これが、コールバック関数が生まれた理由です。関数をパラメータとして別の関数 (この時点では高階関数と呼ばれます) に渡すと、関数内でこの関数を呼び出して、対応するタスクを完了できます。コールバック関数には戻り値がなく (リターンを使用しないでください)、関数内で特定のアクションを実行するためにのみ使用されます。例を見てみましょう:
Something.save(function(err) { if (err) { //error handling return; // 没有返回值 } console.log('success'); });
上記の例では、エラーファースト コールバック関数 (エラーファースト コールバック) を示しています。これは、Node.js 自体の特性の 1 つでもあり、すべてのコア モジュールで使用できます。 Node.js の NPM リポジトリ ほとんどのモジュールは、作成時にこの機能に従います。
コールバック関数の過度の使用によって遭遇する課題:
コードを合理的に整理できない場合、コールバック地獄 (コールバック地獄) が非常に発生しやすく、コードが他の人に理解されにくくなります。
エラー処理コードは見落としがちです。
return ステートメントを使用して値を返すことはできません。また、throw キーワードを使用することもできません。
こうした理由から、JavaScript の世界では、非同期 JavaScript 開発を容易にする実現可能なソリューションを常に探しています。
考えられる解決策の 1 つは、async モジュールです。コールバック関数を長い間扱ってきた人なら、JavaScript で何かを並列または直列に実行したり、非同期関数を使用して配列にマッピング (マッピング) したりする方法について深く理解しているかもしれません。 complex は非同期関数を使用する要素です。したがって、これらの問題を解決する非同期モジュールを作成してくれた Caolan McMahon に感謝します。
async モジュールを使うと、以下のように簡単にコードを書くことができます:
async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ // result will be [1, 4, 9] });
async モジュールはある程度の利便性をもたらしますが、まだ十分に単純ではなく、コードが読みにくいため、Promise が登場します。
現在の JavaScript 非同期標準は 2012 年に遡り、ES6 まで利用可能になりませんでした。ただし、Promise という用語は JavaScript コミュニティによって発明されたものではありません。この用語は、1976 年にダニエル P. フリードマンによって発表された記事に由来しています。
Promise は、非同期操作の最終結果を表します。
ここで、Promise を使用して、上記のコードで完了したタスクを完了します。Promise スタイルのコードは次のとおりです:
Something.save() .then(function() { console.log('success'); }) .catch(function() { //error handling })
Promise ではコールバック関数も使用されていることがわかります。コールバック関数は then メソッドと catch メソッドの両方に渡され、Promise が満たされた場合と拒否された場合にそれぞれ実行されます。 Promise 関数のもう 1 つの利点は、関数を連鎖させて一連のタスクを完了できることです。たとえば、次のようなコードを書くことができます:
saveSomething() .then(updateOtherthing) .then(deleteStuff) .then(logResults);
既製の Promise がない場合は、いくつかの Promise ライブラリを使用する必要がある場合があります。一般的な選択肢は、bluebird を使用することです。これらのライブラリは、ネイティブ ソリューションよりも多くの機能を提供する可能性があり、Promise/A+ 標準で指定された機能に限定されません。
でも、なぜ砂糖を使った方法を使わないのでしょうか?最初に「Promise: 拡張機能の問題」という記事を読むことをお勧めします。 Promise の詳細については、Promise/A+ 標準を参照してください。
「ほとんどのライブラリがコールバック インターフェイスのみを公開する場合、Promise をどのように使用すればよいですか?」と疑問に思うかもしれません。
これは非常に簡単です。現時点で行う必要があるのは、Promise を使用してコールバックを含む関数呼び出し本体をラップすることだけです。例:
コールバック スタイルのコードは次のようになります:
function saveToTheDb(value) { db.values.insert(value, function (err, user) { if (err) throw err; // todo: insert user to db }); }
次に、Promise スタイルの呼び出しをサポートするコードに変更します:
function saveToTheDb(value) { return new Promise(function(resolve, reject) { db.values.insert(value, function(err, user) { // remember error first if (err) { return reject(err); // don't forget to return here } resolve(user); }) } }
両方のメソッドを同時にサポートするライブラリやフレームワークがすでにかなりの数あります。同時に、コールバック スタイルと Promise スタイルの API インターフェイスを提供します。したがって、ライブラリを外部にも提供したい場合、ベスト プラクティスは両方のインターフェイスを同時に提供することです。この目的を達成するには、次の方法を簡単に使用できます:
function foo(cb) { if (cb) { return cb(); } return new Promise(function (resolve, reject) { }); }
或者更简单些,你可以从只提供Promise风格的接口开始后,并使用诸如 callbackify这样的工具来达到向后兼容的目的。其实Callbackify所做的工作和上面的代码片段类似,但在实现上使用了一个更通用的方法, 我建议你可以去阅读Callbackify的源代码。
JavaScript 生成器是个相对较新的概念, 它是ES6(也被称为ES2015)的新特性。想象下面这样的一个场景:
当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。
上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。
function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
更进一步的,如果你想更轻松的使用生成器函数来编写异步JavaScript代码,我们可以使用 co 这个库,co是著名的tj大神写的。
Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。
使用co,前面的示例代码,我们可以使用下面的代码来改写:
co(function* (){ yield Something.save(); }).then(function() { // success }) .catch(function(err) { //error handling });
你可能会问:如何实现并行操作呢?答案可能比你想象的简单,如下(其实它就是Promise.all而已):
yield [Something.save(), Otherthing.save()];
在ES7(还未正式标准化)中引入了Async函数的概念,目前如果你想要使用的话,只能借助于babel 这样的语法转换器将其转为ES5代码。(提醒一点:我们现在讨论的是async关键字,而不是NPM中的async包)。
简而言之,使用async关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作。当然,除了hack之外。
也许你会问,是否在ES7中有了async关键字,yield就变得不是那么重要了?
实际上,使用yield实现异步也不过是一种hack罢了,yield意味着懒次序(lazy sequences)和迭代器。 而await能够完美的分离这两点,首先让yield用于其最初的目的,其次使用await来执行异步操作。
在这背后,async函数实际使用的是Promise,也就是为什么async函数会返回一个Promise的原因。
因此,我们使用async函数来完成类似于前面代码所完成的工作,可以使用下面这样的方式来重新编写代码:
async function save(Something) { try { await Something.save(); // 等待await后面的代码执行完,类似于yield } catch (ex) { //error handling } console.log('success'); }
正如你看到的那样,使用async函数,你需要在函数声明的最前面加上async关键字。这之后,你可以在函数内部使用await关键字了,作用和之前的yield作用是类似的。
使用async函数完成并行任务与yiled的方式非常的相似,唯一不同的是,此时Promise.all不再是隐式的,你需要显示的调用它:
async function save(Something) { await Promise.all[Something.save(), Otherthing.save()] }
Koa也支持async函数,如果你也在使用koa,那么你现在就可以借助babel使用这一特性了。
import koa from koa; let app = koa(); app.experimental = true; app.use(async function (){ this.body = await Promise.resolve('Hello Reader!') }) app.listen(3000);
以上がJavaScript 非同期関数の開発履歴とコード例を詳しく紹介します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。