ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript 非同期進化の歴史のコード例の詳細な紹介
JS の最も基本的な非同期呼び出しメソッドはコールバックです。非同期の完了後、ブラウザまたはノードはコールバック関数のコールバックを非同期 API に渡します。単純な非同期操作の場合は、コールバックを使用するだけで十分です。しかし、インタラクティブなページとノードの出現により、コールバック ソリューションの欠点が明らかになり始めました。 Promise 仕様が誕生し、ES6 仕様に組み込まれました。その後、ES7 では Promise に基づいて非同期機能が標準に組み込まれました。これは JavaScript の非同期進化の歴史です。
通常、コードは上から下に実行されます。複数のタスクがある場合は、次のタスクが実行される前に前のタスクが完了する必要があります。この実行モードは同期と呼ばれます。初心者は、コンピュータ言語での同期と日常言語での同期を簡単に混同してしまいます。たとえば、「ファイルをクラウドに同期する」の同期は、「一貫性を保つ」ことを指します。コンピュータにおいて、同期とは、タスクが上から下に順番に実行されるモードを指します。例:
A(); B(); C();
このコードでは、A、B、C は 3 つの異なる関数であり、各関数は無関係のタスクです。同期モードでは、コンピューターはタスク A を実行し、次にタスク B、最後にタスク C を実行します。ほとんどの場合、同期モードで問題ありません。ただし、タスク B が長時間実行されるネットワーク リクエストで、タスク C がたまたま新しいページを表示している場合、Web ページがフリーズします。
より良い解決策は、タスク B を 2 つの部分に分割することです。 1 つの部分はネットワークから要求されたタスクを即座に実行し、もう 1 つの部分は要求が返された後にタスクを実行します。一方の部分がすぐに実行され、もう一方の部分が後で実行されるこのパターンは、非同期と呼ばれます。
A(); // 在现在发送请求 ajax('url1',function B() { // 在未来某个时刻执行 }) C(); // 执行顺序 A => C => B
実際、JS エンジンはネットワーク リクエストを直接処理するのではなく、ブラウザのネットワーク リクエスト インターフェイスを呼び出すだけで、ブラウザはネットワーク リクエストを送信し、返されたデータを監視します。 JavaScript の非同期機能の本質は、ブラウザまたはノードのマルチスレッド機能です。
将来実行される関数は通常コールバックと呼ばれます。コールバックの非同期モードを使用すると、ブロッキングの問題は解決されますが、他の問題も発生します。当初、関数は上から下に記述され、上から下に実行されていましたが、この「線形」モードは私たちの思考習慣と非常に一致していますが、現在はコールバックによって中断されています。上記のコードでは、タスク B をスキップして、タスク C を最初に実行します。この種の非同期「非線形」コードは、同期「線形」コードよりも読みにくく、したがってバグが発生する可能性が高くなります。
次のコードの実行順序を判断してみてください。「非線形」コードは「線形」コードよりも読みにくいということがより深く理解できるようになります。
A(); ajax('url1', function(){ B(); ajax('url2', function(){ C(); } D(); }); E(); // A => E => B => D => C
このコードでは、上から下への実行順序がコールバックによって乱れています。コードを読むときの視線は A => B => D => E
ですが、実行順序は A => E => です。 B => ; D => C
、これは非線形コードの悪い点です。 A => B => C => D => E
,但是执行顺序却是A => E => B => D => C
,这就是非线性代码带来的糟糕之处。
通过将ajax
后面执行的任务提前,可以更容易看懂代码的执行顺序。虽然代码因为嵌套看起来不美观,但现在的执行顺序却是从上到下的“线性”方式。这种技巧在写多重嵌套的代码时,是非常有用的。
A(); E(); ajax('url1', function(){ B(); D(); ajax('url2', function(){ C(); } }); // A => E => B => D => C
上一段代码只有处理了成功回调,并没处理异常回调。接下来,把异常处理回调加上,再来讨论代码“线性”执行的问题。
A(); ajax('url1', function(){ B(); ajax('url2', function(){ C(); },function(){ D(); }); },function(){ E(); });
加上异常处理回调后,url1
的成功回调函数 B 和异常回调函数 E,被分开了。这种“非线性”的情况又出现了。
在 node 中,为了解决的异常回调导致的“非线性”的问题,制定了错误优先的策略。node 中 callback 的第一个参数,专门用于判断是否发生异常。
A(); get('url1', function(error){ if(error){ E(); }else { B(); get('url2', function(error){ if(error){ D(); }else{ C(); } }); } });
到此,callback 引起的“非线性”问题基本得到解决。遗憾的是,使用 callback 嵌套,一层层if else
和回调函数,一旦嵌套层数多起来,阅读起来不是很方便。此外,callback 一旦出现异常,只能在当前回调函数内部处理异常。
在 JavaScript 的异步进化史中,涌现出一系列解决 callback 弊端的库,而 Promise 成为了最终的胜者,并成功地被引入了 ES6 中。它将提供了一个更好的“线性”书写方式,并解决了异步异常只能在当前回调中被捕获的问题。
Promise 就像一个中介,它承诺会将一个可信任的异步结果返回。首先 Promise 和异步接口签订一个协议,成功时,调用resolve
函数通知 Promise,异常时,调用reject
通知 Promise。另一方面 Promise 和 callback 也签订一个协议,由 Promise 在将来返回可信任的值给then
和catch
ajax
の後に実行されるタスクを進めることで、コードの実行順序が理解しやすくなります。コードは入れ子になっているため見苦しくなりますが、実行順序は上から下へ「線形」になります。この手法は、複数のネストされたコードを作成する場合に非常に役立ちます。 🎜// 创建一个 Promise 实例(异步接口和 Promise 签订协议) var promise = new Promise(function (resolve,reject) { ajax('url',resolve,reject); }); // 调用实例的 then catch 方法 (成功回调、异常回调与 Promise 签订协议) promise.then(function(value) { // success }).catch(function (error) { // error })🎜 前のコードは成功コールバックのみを処理し、例外コールバックは処理しません。次に、例外処理コールバックを追加し、コードの「線形」実行の問題について説明します。 🎜
var promise1 = new Promise(function (resolve) { // 可能由于某些原因导致同步调用 resolve('B'); }); // promise依旧会异步执行 promise1.then(function(value){ console.log(value) }); console.log('A'); // A B (先 A 后 B) var promise2 = new Promise(function (resolve) { // 成功回调被通知了2次 setTimeout(function(){ resolve(); },0) }); // promise只会调用一次 promise2.then(function(){ console.log('A') }); // A (只有一个) var promise3 = new Promise(function (resolve,reject) { // 成功回调先被通知,又通知了失败回调 setTimeout(function(){ resolve(); reject(); },0) }); // promise只会调用成功回调 promise3.then(function(){ console.log('A') }).catch(function(){ console.log('B') }); // A(只有A)🎜例外処理コールバックを追加すると、
url1
の成功コールバック関数Bと例外コールバック関数Eが分離されます。この「非線形」状況が再び現れます。 🎜🎜ノードでは、異常なコールバックによって引き起こされる「非線形」問題を解決するために、エラー優先戦略が策定されました。ノードのコールバックの最初のパラメータは、例外が発生したかどうかを判断するために特に使用されます。 🎜var fetch = function(url){ // 返回一个新的 Promise 实例 return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } A(); fetch('url1').then(function(){ B(); // 返回一个新的 Promise 实例 return fetch('url2'); }).catch(function(){ // 异常的时候也可以返回一个新的 Promise 实例 return fetch('url2'); // 使用链式写法调用这个新的 Promise 实例的 then 方法 }).then(function() { C(); // 继续返回一个新的 Promise 实例... }) // A B C ...🎜この時点で、コールバックによって引き起こされる「非線形」問題は基本的に解決されました。残念ながら、コールバックのネスト、
if else
のレイヤー、およびコールバック関数を使用すると、ネストされたレイヤーの数が増えると、読み取りがあまり便利ではなくなります。さらに、コールバックで例外が発生すると、その例外は現在のコールバック関数内でのみ処理できます。 🎜🎜promise🎜🎜 JavaScript の非同期進化の歴史の中で、コールバックの欠点を解決するために一連のライブラリが登場し、Promise が最終的な勝者となり、ES6 への導入に成功しました。これにより、より優れた「線形」の記述方法が提供され、非同期例外が現在のコールバックでのみキャッチできるという問題が解決されます。 🎜🎜Promise は、信頼できる非同期結果を返すことを約束する仲介者のようなものです。まず、Promise は非同期インターフェースとの合意に署名します。成功すると、resolve
関数が呼び出され、例外が発生すると、reject
が呼び出され、Promise に通知されます。一方で、Promise とコールバックも契約を締結しており、将来的に Promise は then
と catch
に登録されたコールバックに信頼できる値を返すことになります。 🎜// 创建一个 Promise 实例(异步接口和 Promise 签订协议) var promise = new Promise(function (resolve,reject) { ajax('url',resolve,reject); }); // 调用实例的 then catch 方法 (成功回调、异常回调与 Promise 签订协议) promise.then(function(value) { // success }).catch(function (error) { // error })
Promise 是个非常不错的中介,它只返回可信的信息给 callback。它对第三方异步库的结果进行了一些加工,保证了 callback 一定会被异步调用,且只会被调用一次。
var promise1 = new Promise(function (resolve) { // 可能由于某些原因导致同步调用 resolve('B'); }); // promise依旧会异步执行 promise1.then(function(value){ console.log(value) }); console.log('A'); // A B (先 A 后 B) var promise2 = new Promise(function (resolve) { // 成功回调被通知了2次 setTimeout(function(){ resolve(); },0) }); // promise只会调用一次 promise2.then(function(){ console.log('A') }); // A (只有一个) var promise3 = new Promise(function (resolve,reject) { // 成功回调先被通知,又通知了失败回调 setTimeout(function(){ resolve(); reject(); },0) }); // promise只会调用成功回调 promise3.then(function(){ console.log('A') }).catch(function(){ console.log('B') }); // A(只有A)
介绍完 Promise 的特性后,来看看它如何利用链式调用,解决异步代码可读性的问题的。
var fetch = function(url){ // 返回一个新的 Promise 实例 return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } A(); fetch('url1').then(function(){ B(); // 返回一个新的 Promise 实例 return fetch('url2'); }).catch(function(){ // 异常的时候也可以返回一个新的 Promise 实例 return fetch('url2'); // 使用链式写法调用这个新的 Promise 实例的 then 方法 }).then(function() { C(); // 继续返回一个新的 Promise 实例... }) // A B C ...
如此反复,不断返回一个 Promise 对象,再采用链式调用的方式不断地调用。使 Promise 摆脱了 callback 层层嵌套的问题和异步代码“非线性”执行的问题。
Promise 解决的另外一个难点是 callback 只能捕获当前错误异常。Promise 和 callback 不同,每个 callback 只能知道自己的报错情况,但 Promise 代理着所有的 callback,所有 callback 的报错,都可以由 Promise 统一处理。所以,可以通过catch
来捕获之前未捕获的异常。
Promise 解决了 callback 的异步调用问题,但 Promise 并没有摆脱 callback,它只是将 callback 放到一个可以信任的中间机构,这个中间机构去链接我们的代码和异步接口。
异步(async)函数是 ES7 的一个新的特性,它结合了 Promise,让我们摆脱 callback 的束缚,直接用类同步的“线性”方式,写异步函数。
声明异步函数,只需在普通函数前添加一个关键字 async
即可,如async function main(){}
。在异步函数中,可以使用await
关键字,表示等待后面表达式的执行结果,一般后面的表达式是 Promise 实例。
async function main{ // timer 是在上一个例子中定义的 var value = await timer(100); console.log(value); // done (100ms 后返回 done) } main();
异步函数和普通函数一样调用 main()
。调用后,会立即执行异步函数中的第一行代码 var value = await timer(100)
。等到异步执行完成后,才会执行下一行代码。
除此之外,异步函数和其他函数基本类似,它使用try...catch
来捕捉异常。也可以传入参数。但不要在异步函数中使用return
来返回值。
var timer = new Promise(function create(resolve,reject) { if(typeof delay !== 'number'){ reject(new Error('type error')); } setTimeout(resolve,delay,'done'); }); async function main(delay){ try{ var value1 = await timer(delay); var value2 = await timer(''); var value3 = await timer(delay); }catch(err){ console.error(err); // Error: type error // at create (<anonymous>:5:14) // at timer (<anonymous>:3:10) // at A (<anonymous>:12:10) } } main(0);
异步函数也可以被当作值,传入普通函数和异步函数中执行。但是在异步函数中,使用异步函数时要注意,如果不使用await
,异步函数会被同步执行。
async function main(delay){ var value1 = await timer(delay); console.log('A') } async function doAsync(main){ main(0); console.log('B') } doAsync(main); // B A
这个时候打印出来的值是 B A
。说明 doAsync
函数并没有等待 main
的异步执行完毕就执行了 console
。如果要让 console
在main
的异步执行完毕后才执行,我们需要在main
前添加关键字await
。
async function main(delay){ var value1 = await timer(delay); console.log('A') } async function doAsync(main){ await main(0); console.log('B') } doAsync(main); // A B
由于异步函数采用类同步的书写方法,所以在处理多个并发请求,新手可能会像下面一样书写。这样会导致url2
的请求必需等到url1
的请求回来后才会发送。
var fetch = function (url) { return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } async function main(){ try{ var value1 = await fetch('url1'); var value2 = await fetch('url2'); conosle.log(value1,value2); }catch(err){ console.error(err) } } main();
使用Promise.all
的方法来解决这个问题。Promise.all
用于将多个Promise实例,包装成一个新的 Promis e实例,当所有的 Promise 成功后才会触发Promise.all
的resolve
函数,当有一个失败,则立即调用Promise.all
的reject
函数。
var fetch = function (url) { return new Promise(function (resolve,reject) { ajax(url,resolve,reject); }); } async function main(){ try{ var arrValue = await Promise.all[fetch('url1'),fetch('url2')]; conosle.log(arrValue[0],arrValue[1]); }catch(err){ console.error(err) } } main();
目前使用 Babel 已经支持 ES7 异步函数的转码了,大家可以在自己的项目中开始尝试。
以上就是JavaScript 异步进化史的代码实例详细介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!