ホームページ > ウェブフロントエンド > jsチュートリアル > JavaScript非同期関数の開発経緯を語る_JavaScriptスキル

JavaScript非同期関数の開発経緯を語る_JavaScriptスキル

WBOY
リリース: 2016-05-16 15:37:30
オリジナル
1477 人が閲覧しました

外国語の「非同期 JavaScript の進化」では、JavaScript 非同期関数の開発の歴史を整理しました。まず、非同期関数はコールバック関数を通じて実現され、その後、Promise/A およびジェネレーター関数を経て実現されることになります。非同期関数であること。この記事を翻訳してくれた Jingzhuang に感謝します。内容は次のとおりです:

ここで、長年にわたる JavaScript 非同期関数の開発を振り返ってみましょう。

コールバック関数 Callbacks

コールバック関数からすべてを開始する必要があるようです。

非同期 JavaScript

ご存知のとおり、JavaScript では、非同期プログラミングは JavaScript 言語の第一級市民関数を通じてのみ実行できます。この方法は、ある関数を別の関数のパラメーターとして渡すことができることを意味します。関数内で呼び出されます (つまり、コールバック関数)。これが、コールバック関数が生まれた理由です。関数をパラメータとして別の関数 (この時点では高階関数と呼ばれます) に渡すと、関数内でこの関数を呼び出して、対応するタスクを完了できます。コールバック関数には戻り値がなく (リターンを使用しないでください)、関数内で特定のアクションを実行するためにのみ使用されます。 例を見てみましょう:

Something.save(function(err) {  
 if (err) { 
  //error handling 
  return; // 没有返回值 
 } 
 console.log('success'); 
}); 
ログイン後にコピー

上記の例では、Node.js のすべてのコア モジュールと NPM リポジトリのほとんどのモジュールの特徴の 1 つでもあるエラー優先コールバック関数 (error-first callbacks) を示しました。書き込み時にはこの機能に従います。

コールバック関数の多用に関する課題:

コードを合理的に整理できないと、コールバック地獄 (コールバック地獄) が発生しやすくなり、コードが他の人に理解されにくくなります。
エラー処理コードは見落とされがちです。

return ステートメントを使用して値を返すことはできません。また、throw キーワードは使用できません。

これらの理由から、JavaScript の世界では、非同期 JavaScript 開発を容易にする実現可能なソリューションを常に探しています。

考えられる解決策の 1 つは、async モジュールです。コールバック関数を長い間扱ってきた人であれば、JavaScript で何かを並列または直列に実行したり、非同期関数を使用して配列内の何かをマップしたりする場合、どれほど複雑であるかを深く理解しているかもしれません。は非同期関数を使用する要素です。したがって、これらの問題を解決する非同期モジュールを作成してくれた 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的源代码。

生成器Generators/ yield

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()];  
Async/ await
ログイン後にコピー

在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.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート