ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript 非同期関数の開発履歴とコード例を詳しく紹介します。

JavaScript 非同期関数の開発履歴とコード例を詳しく紹介します。

黄舟
黄舟オリジナル
2017-03-11 15:12:141656ブラウズ

ほとんどの JavaScript 開発者にとって、非同期関数は新しいものであり、その開発には長い道のりを経てきました。そこで、この記事では、JavaScript の非同期 関数の開発の歴史を整理してまとめてみます。少し前までは、非同期を実現するにはコールバック関数しか記述できませんでしたが、その後、Promise/A+ 標準が登場しました。その後、ジェネレーター関数が登場しました。 、そして将来は明らかに非同期関数です。

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

Callbacks

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

非同期 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 が登場します。

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(&#39;success&#39;);  
}

正如你看到的那样,使用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(&#39;Hello Reader!&#39;)  
})  

app.listen(3000);

以上がJavaScript 非同期関数の開発履歴とコード例を詳しく紹介します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。