JavaScript 非同步進化史的程式碼實例詳細介紹
前言
JS 中最基礎的非同步呼叫方式是callback,它將回呼函數callback 傳給非同步API,由瀏覽器或Node 在非同步完成後,通知JS 引擎調用callback。對於簡單的非同步操作,用 callback 實現,是夠用的。但隨著負責互動頁面和 Node 出現,callback 方案的弊端開始浮現。 Promise 規範孕育而生,並納入 ES6 的規範中。後來 ES7 又在 Promise 的基礎上將 async 函數納入標準。此為 JavaScript 非同步進化史。
同步與非同步
通常,程式碼是由上往下依序執行的。如果有多個任務,就必須排隊,前一個任務完成,後一個任務才會執行。這種執行模式稱之為:同步(synchronous)。新手容易把計算機用語中的同步,和日常用語中的同步混淆。如,「把檔案同步到雲端」中的同步,指的是「使…保持一致」。而在計算機中,同步指的是任務從上往下依序執行的模式。例如:
A(); B(); C();
在這段程式碼中,A、B、C是三個不同的函數,每個函數都是一個不相關的任務。在同步模式,電腦會先執行 A 任務,再執行 B 任務,最後執行 C 任務。在大部分情況,同步模式都沒問題。但如果 B 任務是耗時很長的網路請求,而 C 任務恰好是展現新頁面,就會導致網頁卡頓。
更好解決方案是,將 B 任務分成兩個部分。一部分立即執行網路請求的任務,另一部分在請求回來後的執行任務。這種一部分立即執行,另一部分在未來執行的模式稱為非同步。
A(); // 在现在发送请求 ajax('url1',function B() { // 在未来某个时刻执行 }) C(); // 执行顺序 A => C => B
實際上,JS 引擎並沒有直接處理網路請求的任務,它只是調用了瀏覽器的網路請求接口,由瀏覽器發送網路請求並監聽返回的資料。 JavaScript 非同步能力的本質是瀏覽器或 Node 的多執行緒能力。
callback
未來執行的函數通常也叫 callback。使用 callback 的非同步模式,解決了阻塞的問題,但也帶來了一些其他問題。在最開始,我們的函數是從上往下書寫的,也是從上往下執行的,這種「線性」模式,非常符合我們的思維習慣,但是現在卻被 callback 打斷了!在上面一段程式碼中,現在它跳過 B 任務先執行了 C任務!這種非同步「非線性」的程式碼會比同步「線性」的程式碼,更難閱讀,因此也更容易滋生 BUG。
試著判斷下面這段程式碼的執行順序,你會對「非線性」程式碼比「線性」程式碼更難閱讀,體會更深。
A(); ajax('url1', function(){ B(); ajax('url2', function(){ C(); } D(); }); E(); // A => E => B => D => C
這段程式碼中,從上往下執行的順序被 Callback 打亂了。我們的閱讀程式碼視線是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 一旦出現異常,只能在目前回呼函數內部處理異常。
promise
在 JavaScript 的非同步進化史中,湧現出一系列解決 callback 弊端的函式庫,而 Promise 成為了最終的勝者,並成功地被引入了 ES6 中。它將提供了一個更好的「線性」書寫方式,並解決了非同步異常只能在當前回呼中被捕獲的問題。
Promise 就像一個中介,它承諾會將一個可信任的非同步結果回傳。首先 Promise 和非同步介面簽訂一個協議,成功時,呼叫resolve
函數通知 Promise,異常時,呼叫reject
通知 Promise。另一方面 Promise 和 callback 也簽訂一個協議,由 Promise 在將來返回可信任的值給then
和catch
中註冊的 callback。
// 创建一个 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)函数
异步(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)!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數
