この記事では、JS 非同期プログラミングについて、主に Promise、Generator、async/await について紹介します。必要な友人は参考にしてください。
前回の記事では、非同期とは何か、非同期プログラミングを使用する理由、ブラウザーで JS の非同期プログラミングを実装する方法など、JS 非同期プログラミングの関連知識について説明しました。
最後に、いくつかの JS 非同期プログラミング モード (コールバック、イベント、パブリッシュ/サブスクライブ モード) について説明しました。この記事では、他のいくつかの非同期プログラミング モードについて引き続き詳しく見ていきます。
Promise
Promise は、ES6 で導入された非同期プログラミング ソリューションです。実際、ES6 よりも前に、多くの非同期ツール ライブラリがさまざまな同様のソリューションを実装していました。ES6 はそれらを言語標準に書き込み、その使用方法を統一しました。 Promise は、コールバックなどのネストされたソリューションの問題を解決し、コードを読みやすくし、同期メソッドを作成するときに既視感を与えます。
まず、ES6 での Promise の使用法を簡単に理解しましょう
var p = new Promise(function async(resolve, reject){ // 这里是你的异步操作 setTimeout(function(){ if(true){ resolve(val); }else{ reject(error); } }, 1000) }) p.then(function(val){ console.log('resolve'); }, function(){ console.log('reject'); })
まず第一に、ES6 では Promise がコンストラクターであると規定されており、async
関数。この関数には、成功と失敗の 2 つの状態にそれぞれ対応する、解決と拒否という 2 つのパラメータがあります。次のアクションをトリガーして関数を実行するために、異なる時点で解決または拒否を実行することを選択できます。そのときの方法。 async
函数,此函数有两个参数,resolve、reject分别对应成功失败两种状态,我们可以选择在不同时候执行resolve或者reject去触发下一个动作,执行then方法里的函数。
我们可以简单对比下回调的写法和promise的写法的不同
对于传统回调写法来说,一般会写成这样
asyncFn1(function () { asyncFn2(function() { asyncFn3(function() { // xxxxx }); }); });
或者我们将各个回调函数拆出来独立来写以减少耦合,像是这样:
function asyncFn1(callback) { return function() { console.log('asyncFn1 run'); setTimeout(function(){ callback(); }, 1000); } }function asyncFn2(callback) { return function(){ console.log('asyncFn2 run'); setTimeout(function(){ callback(); }, 1000); } }function normalFn3() { console.log('normalFn3 run'); } asyncFn1(asyncFn2(normalFn3))()
最后我们看下Promise的写法
function asyncFn1() { console.log('asyncFn1 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) }function asyncFn2() { console.log('asyncFn2 run'); return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(); }, 1000) }) }function normalFn3() { console.log('normalFn3 run'); } asyncFn1().then(asyncFn2).then(normalFn3);
这样来看无论是第一种还是第二种写法,都会让人感到不是很直观,而Promise的写法更加直观和语义化。
Generator
Generator函数也是ES6提供的一种特殊的函数,其语法行为与传统函数完全不同。
我们先直观看个Generator实际的用法
function* oneGenerator() { yield 'Learn'; yield 'In'; return 'Pro'; }var g = oneGenerator(); g.next(); // {value: "Learn", done: false}g.next(); // {value: "In", done: false}g.next(); // {value: "Pro", done: true}
Generator函数是一种特殊的函数,他有这么几个特点:
声明时需要在
コールバックの書き方とPromiseの書き方の違いを単純に比較することができます🎜🎜🎜従来のコールバックの書き方では、通常次のように記述されます🎜🎜function
后面加上*
,并且配合函数里面yield
🎜🎜または、各コールバック関数を分離して独立して記述することもできますカップリングを減らすには次のようにします: 🎜🎜// 使用Generator函数进行异步编程function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn3(); }// 我们来对比一下PromiseasyncFn1().then(asyncFn2).then(normalFn3);ログイン後にコピーログイン後にコピー🎜🎜 最後に、Promise の書き方を見てみましょう🎜🎜var g;function asyncFn() { setTimeout(function(){ g.next(); }, 1000) }function normalFn() { console.log('normalFn run'); }function* oneGenerator() { yield asyncFn(); return normalFn(); } g = oneGenerator(); g.next();// 这里在我调用next方法的时候执行了asyncFn函数// 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。// 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。ログイン後にコピーログイン後にコピー🎜 このように、最初の書き方であっても 2 番目の書き方であっても、人々にそう感じさせます。 Promise の記述方法は、あまり直感的ではなく、より直感的で意味的な変更が行われます。 🎜🎜🎜Generator🎜🎜🎜Generator関数もES6が提供する特殊な関数であり、その文法的な動作は従来の関数とは全く異なります。 🎜🎜まずは Generator の実際の使い方を見てみましょう🎜// 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。function autoGenerator(generator){ var g = generator(); function next(){ var res = g.next(); // {value: xxx, done: xxx} if (res.done) { return res.value; } if(typeof res.value === 'function'){ // 认为是回调 res.value(next); }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){ // 认为是promise res.value.then(function(){ next(); }) }else{ next(); } } next(); }// ----function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve(); }, 1000) }) }function asyncFn2() { console.log('asyncFn2'); return function(callback){ setTimeout(function(){ callback(); }, 1000); } }function normalFn() { console.log('normalFn'); }function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn(); } autoGenerator(oneGenerator);ログイン後にコピーログイン後にコピー🎜 Generator 関数はいくつかの特徴があります:🎜🎜function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve('123'); }, 2000) }) }function asyncFn2() { console.log('asyncFn2'); return new Promise(function(resolve){ setTimeout(function(){ resolve('456'); }, 2000) }) } async function asyncFn () { var a = await asyncFn1(); var b = await asyncFn2(); console.log(a,b) } asyncFn();// asyncFn1// asyncFn2// 123,456ログイン後にコピーログイン後にコピー
*
、到下一个yield或者return或者函数体尾部
之间的代码,并且将yield后面的值,包装成json对象返回。就像上面的例子中的{value: xxx, done: xxx}
。value取的yield或者return后面的值,否则就是undefined,done的值如果碰到return或者执行完成则返回true,否则返回false。
我们知道了简单的Generator函数的用法以后,我们来看下如何使用Generator函数进行异步编程。
首先我们先来看下使用Generator函数能达到怎样的效果。
// 使用Generator函数进行异步编程function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn3(); }// 我们来对比一下PromiseasyncFn1().then(asyncFn2).then(normalFn3);
我们可以看出使用Generator函数进行异步编程更像是在写同步任务,对比Promise少了很多次then方法的调用。
好,那么接下来我们就来看下如何实际使用Generator函数进行异步编程。
这里我要特别说明一下,事实上Generator函数不像Promise一样是专门用来解决异步处理而产生的,人们只是使用其特性来产出了一套异步的解决方案,所以使用Generator并不像使用Promise一样有一种开箱即用的感觉。其更像是在Promise或者回调这类的解决方案之上又封装了一层,让你可以像上面例子里一样去那么写。
我们还是具体来看下上面的例子,我们知道单写一个Generator是不能运行的对吧,我们需要执行他并且使用next方法来让他分步执行,那么什么时候去调用next呢?答案就是我们需要在异步完成时去调用next。我们来按照这个思路补全上面的例子。
var g;function asyncFn() { setTimeout(function(){ g.next(); }, 1000) }function normalFn() { console.log('normalFn run'); }function* oneGenerator() { yield asyncFn(); return normalFn(); } g = oneGenerator(); g.next();// 这里在我调用next方法的时候执行了asyncFn函数// 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。// 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。
那么怎么解决呢,我们需要自己写个方法,能自动运行Generator函数,这种方法很简单在社区里有很多,最著名的就是大神TJ写的co模块,有兴趣的同学可以看下其源码实现。这里我们简单造个轮子:
// 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。function autoGenerator(generator){ var g = generator(); function next(){ var res = g.next(); // {value: xxx, done: xxx} if (res.done) { return res.value; } if(typeof res.value === 'function'){ // 认为是回调 res.value(next); }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){ // 认为是promise res.value.then(function(){ next(); }) }else{ next(); } } next(); }// ----function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve(); }, 1000) }) }function asyncFn2() { console.log('asyncFn2'); return function(callback){ setTimeout(function(){ callback(); }, 1000); } }function normalFn() { console.log('normalFn'); }function* oneGenerator() { yield asyncFn1(); yield asyncFn2(); yield normalFn(); } autoGenerator(oneGenerator);
这个方法我们简单实现了最核心的部分,有些判断可能并不严谨,但大家理解这个思路就可以了。有了这个方法,我们才可以方便的使用Generator函数进行异步编程。
Async/Await
如果你学会了Generator函数,对于Async函数就会很容易上手。你可以简单把Async函数理解成就是Generator函数+执行器。我们就直接上实例好了
function asyncFn1(){ console.log('asyncFn1'); return new Promise(function(resolve){ setTimeout(function(){ resolve('123'); }, 2000) }) }function asyncFn2() { console.log('asyncFn2'); return new Promise(function(resolve){ setTimeout(function(){ resolve('456'); }, 2000) }) } async function asyncFn () { var a = await asyncFn1(); var b = await asyncFn2(); console.log(a,b) } asyncFn();// asyncFn1// asyncFn2// 123,456
当然async里实现的执行器肯定是跟咱们上面简单实现的有所不同,所以在用法上也会有些注意的点
首先async函数的返回值是一个Promise对象,不像是generator函数返回的是Iterator遍历器对象,所以async函数执行后可以继续使用then等方法来继续进行下面的逻辑
await の後には通常、Promise オブジェクトが続きます。async 関数が実行されると、await が発生した後、後続の Promise オブジェクトのステータスが保留中から解決済みに変わるまで待機し、その後、resolve のパラメーターを返し、自動的に実行を継続します。次の await または End
await までは、非同期関数でネストすることもできます。
非同期については、例外処理、複数非同期並列実行など、カバーしていない知識ポイントがまだたくさんあります。この記事と前の記事は主に、誰もが非同期プログラミングを直感的に理解できることを願っています。 、さまざまなソリューションの違いと長所と短所を理解します。スペースとエネルギーに限りがあるため、ここで取り上げていないその他の知識については、ご興味があり、機会があれば、別の記事で詳しく説明します。
上記がこの記事の全内容です。その他の関連コンテンツについては、PHP 中国語 Web サイトをご覧ください。
関連する推奨事項:
以上がJS 非同期プログラミング Promise、Generator、async/awaitの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。