摘要:所謂異步,就是調用在發出后,這個調用就直接返回了,調用者不會立即得到結果,但是不會阻塞,可以繼續執行后續操作,而被調用者執行得到結果后通過狀態事件來通知調用者使用回調函數來處理這個結果。另外狀態的回調函數是可省略的。
首先明確一個問題,為什么 Node.js 需要異步編程?
JavaScript 是單線程的,在發出一個調用時,在沒有得到結果之前,該調用就不返回,意思就是調用者主動等待調用結果,換句話說,就是必須等待上一個任務執行完才能執行下一個任務,這種執行模式叫:同步。
Node.js 的主要應用場景是處理高并發(單位時間內極大的訪問量)和 I/O 密集場景(ps: I/O 操作往往非常耗時,所以異步的關鍵在于解決 I/O 耗時問題),如果采用同步編程,問題就來了,服務器處理一個 I/O 請求需要大量的時間,后面的請求都將排隊,造成瀏覽器端的卡頓。異步編程能解決這個問題。
所謂異步,就是調用在發出后,這個調用就直接返回了,調用者不會立即得到結果,但是不會阻塞,可以繼續執行后續操作,而被調用者執行得到結果后通過狀態、事件來通知調用者使用回調函數( callback )來處理這個結果。Node在處理耗時的 I/O 操作時,將其交給其他線程處理,自己繼續處理其他訪問請求,當 I/O 操作處理好后就會通過事件通知 Node 用回調做后續處理。
有個例子非常好:
你打電話問書店老板有沒有《分布式系統》這本書,如果是同步通信機制,書店老板會說,你稍等,”我查一下",然后開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。而異步通信機制,書店老板直接告訴你我查一下啊,查好了打電話給你,然后直接掛電話了(不返回結果)。然后查好了,他會主動打電話給你。在這里老板通過“回電”這種方式來回調。
下面幾種方式是異步解決方案的進化過程:
CallBacks回調函數就是函數A作為參數傳遞給函數B,并且在未來某一個時間被調用。callback的異步模式最大的問題就是,理解困難加回調地獄(callback hell),看下面的代碼的執行順序:
A(); ajax("url1", function(){ B(); ajax("url2", function(){ C(); } D(); }); E();
其執行順序為:A => E => B => D => C,這種執行順序的確會讓人頭腦發昏,另外由于由于多個異步操作之間往往會耦合,只要中間一個操作需要修改,那么它的上層回調函數和下層回調函數都可能要修改,這就陷入了回調地獄。而 Promise 對象就很好的解決了異步操作之間的耦合問題,讓我們可以用同步編程的方式去寫異步操作。
PromisePromise 對象是一個構造函數,用來生成promise實例。Promise 代表一個異步操作,有三種狀態:pending,resolved(異步操作成功由 pending 變為 resolved ),rejected(異步操作失敗由 pending 變為 rejected ),一旦變為后兩種狀態將不會再改變。Promise 對象作為構造函數接受一個函數作為參數,而這個函數又接受 resolve 和 reject 兩個函數做為參數,這兩個函數是JS內置的,無需配置。resolve 函數在異步操作成功后調用,將pending狀態變為resolved,并將它的參數傳遞給回調函數;reject 函數在異步操作失敗時調用,將pending狀態變為rejected,并將參數傳遞給回調函數。
Promise.prototype.then()
Promise構造函數的原型上有一個then方法,它接受兩個函數作為參數,分別是 resolved 狀態和 rejected 狀態的回調函數,而這兩個回調函數接受的參數分別是Promise實例中resolve函數和reject函數中的參數。 另外rejected狀態的回調函數是可省略的。
下面是一個使用示例:
const instance = new Promise((resolve, reject) => { // 一些異步操作 if(/*異步操作成功*/) { resolve(value); } else { reject(error); } } }) instance.then(value => { // do something... }, error => { // do something... })
注意Promise實例在生成后會立即執行,而 then 方法只有在所有同步任務執行完后才會執行,看看下面的例子:
const promise = new Promise((resolve, reject) => { console.log("async task begins!"); setTimeout(() => { resolve("done, pending -> resolved!"); }, 1000); }) promise.then(value => { console.log(value); }) console.log("1.please wait"); console.log("2.please wait"); console.log("3.please wait"); // async task begins! // 1.please wait // 2.please wait // 3.please wait // done, pending -> resolved!
上面的實例可以看出,Promise實例生成后立即執行,所以首先輸出 "async task begins!",隨后定義了一個異步操作 setTimeout,1秒后執行,所以無需等待,向下執行,而then方法指定的回調函數要在所有同步任務執行完后才執行,所以先輸出了3個"please wait",最后輸出"done, pending -> resolved!"。(此處省略了then方法中的reject回調,一般不在then中做rejected狀態的處理,而使用catch方法專門處理錯誤,相當于.then(null, reject))
鏈式調用 then 方法
then 方法會返回一個新的 Promise 實例,可以分兩種情況來看:
指定返回值是新的 Promise 對象,如return new Promise(...),這種情況沒啥好說的,由于返回的是 Promise,后面顯然可以繼續調用then方法。
返回值不是Promise, 如:return 1 這種情況還是會返回一個 Promise,并且這個Promise 立即執行回調 resolve(1)。所以仍然可以鏈式調用then方法。(注:如果沒有指定return語句,相當于返回了undefined)
使用 then 的鏈式寫法,按順序實現一系列的異步操作,這樣就可以用同步編程的形式去實現異步操作,來看下面的例子,實現隔兩秒打一次招呼:
function sayHi(name) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(name); }, 2000) }) } sayHi("張三") .then(name => { console.log(`你好, ${name}`); return sayHi("李四"); // 最終 resolved 函數中的參數將作為值傳遞給下一個then }) // name 是上一個then傳遞出來的參數 .then(name => { console.log(`你好, ${name}`); return sayHi("王二麻子"); }) .then(name => { console.log(`你好, ${name}`); }) // 你好, 張三 // 你好, 李四 // 你好, 王二麻子
可以看到使用鏈式then的寫法,將異步操作變成了同步的形式,但是也帶來了新的問題,就是異步操作變成了很長的then鏈,新的解決方法就是Generator,這里跨過它直接說它的語法糖:async/await。
async/awaitasync
async/await實際上是Generator的語法糖。顧名思義,async關鍵字代表后面的函數中有異步操作,await表示等待一個異步方法執行完成。聲明異步函數只需在普通函數前面加一個關鍵字async即可,如:
async function funcA() {}
async 函數返回一個Promise對象(如果指定的返回值不是Promise對象,也返回一個Promise,只不過立即 resolve ,處理方式同 then 方法),因此 async 函數通過 return 返回的值,會成為 then 方法中回調函數的參數:
async function funcA() { return "hello!"; } funcA().then(value => { console.log(value); }) // hello!
多帶帶一個 async 函數,其實與Promise執行的功能是一樣的,來看看 await 都干了些啥。
await
顧名思義, await 就是異步等待,它等待的是一個Promise,因此 await 后面應該寫一個Promise對象,如果不是Promise對象,那么會被轉成一個立即 resolve 的Promise。 async 函數被調用后就立即執行,但是一旦遇到 await 就會先返回,等到異步操作執行完成,再接著執行函數體內后面的語句??偨Y一下就是:async函數調用不會造成代碼的阻塞,但是await會引起async函數內部代碼的阻塞??纯聪旅孢@個例子:
async function func() { console.log("async function is running!"); const num1 = await 200; console.log(`num1 is ${num1}`); const num2 = await num1+ 100; console.log(`num2 is ${num2}`); const num3 = await num2 + 100; console.log(`num3 is ${num3}`); } func(); console.log("run me before await!"); // async function is running! // run me before await! // num1 is 200 // num2 is 300 // num3 is 400
可以看出調用 async func 函數后,它會立即執行,首先輸出了"async function is running!",接著遇到了 await 異步等待,函數返回,先執行func()后面的同步任務,同步任務執行完后,接著await等待的位置繼續往下執行??梢哉f,async函數完全可以看作多個異步操作,包裝成的一個Promise 對象,而await命令就是內部then命令的語法糖。
值得注意的是, await 后面的 Promise 對象不總是返回 resolved 狀態,只要一個 await 后面的Promise狀態變為 rejected ,整個 async 函數都會中斷執行,為了保存錯誤的位置和錯誤信息,我們需要用 try...catch 語句來封裝多個 await 過程,如下:
async function func() { try { const num1 = await 200; console.log(`num1 is ${num1}`); const num2 = await Promise.reject("num2 is wrong!"); console.log(`num2 is ${num2}`); const num3 = await num2 + 100; console.log(`num3 is ${num3}`); } catch (error) { console.log(error); } } func(); // num1 is 200 // 出錯了 // num2 is wrong!
如上所示,在 num2 處 await 得到了一個狀態為 rejected 的Promise對象,該錯誤會被傳遞到 catch 語句中,這樣我們就可以定位錯誤發生的位置。
async/await比Promise強在哪兒?
接下來我們用async/await改寫一下Promise章節中關于sayHi的一個例子,代碼如下:
function sayHi(name) { return new Promise((resolved, rejected) => { setTimeout(() => { resolved(name); }, 2000) }) } async function sayHi_async(name) { const sayHi_1 = await sayHi(name) console.log(`你好, ${sayHi_1}`) const sayHi_2 = await sayHi("李四") console.log(`你好, ${sayHi_2}`) const sayHi_3 = await sayHi("王二麻子") console.log(`你好, ${sayHi_3}`) } sayHi_async("張三") // 你好, 張三 // 你好, 李四 // 你好, 王二麻子
與之前長長的then鏈和then方法里的回調函數相比,這樣的寫法看起來像是同步寫法并且更加清爽,更加符合編程習慣。
參考文章https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://www.zhihu.com/questio...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/95952.html
摘要:第部分畫圖一步步看清宏任務微任務的執行過程我們以開篇的經典面試題為例,分析這個例子中的宏任務和微任務。注意這里只是把推入微任務隊列,并沒有執行。執行結束,才能繼續執行后面的代碼如圖此時當前宏任務都執行完了,要處理微任務隊列里的代碼。 8張圖讓你一步步看清 async/await 和 promise 的執行順序 為什么寫這篇文章? 測試一下自己有沒有必要看 需要具備的前置基礎知識 主...
摘要:因為函數返回一個對象,所以可以用于等待一個函數的返回值這也可以說是在等函數,但要清楚,它等的實際是一個返回值。幫我們干了啥作個簡單的比較上面已經說明了會將其后的函數函數表達式或的返回值封裝成一個對象,而會等待這個完成,并將其的結果返回出來。 隨著 Node 7 的發布,越來越多的人開始研究據說是異步編程終級解決方案的 async/await。我第一次看到這組關鍵字并不是在 JavaSc...
摘要:在異步編程中,提供了對象的方式。例如等同于所以可以理解為生成一個實例。那么自然也可以去調用則是配合使用的。等于是等待一個返回值,等待的執行結果。但是函數不會造成阻塞,所以配合使用,則沒有影響到外部。 在異步編程中,es6提供了promise對象的方式。簡單的用法 var promise = new Promise((resolve,reject)=>{ if(){ ...
摘要:所以是在一秒后顯示的。這個行為不會耗費資源,因為引擎可以同時處理其他任務執行其他腳本,處理事件等。每個回調首先被放入微任務隊列然后在當前代碼執行完成后被執行。,函數是異步的,但是會立即運行。否則,就返回結果,并賦值。 「async/await」是 promises 的另一種更便捷更流行的寫法,同時它也更易于理解和使用。 Async functions 讓我們以 async 這個關鍵字開...
摘要:問題的關鍵在于其執行過程中的微任務數量,下文中我們需要用上述代碼中的方式對微任務的執行順序進行標記,以輔助我們理解這其中的執行過程。 原文發布在掘金社區:https://juejin.im/post/5c3cc981f265da616a47e028 起源 2019年了,相信大家對 Promise 和 async/await 都不再陌生了。 前幾日,我在社區讀到了一篇關于 async/...
摘要:而函數的命令后面則可以是或者原始類型的值,,,但這時等同于同步操作返回值是。拋出的錯誤而會被方法回調函數接收到。 ES7 提出的async 函數,終于讓 JavaScript 對于異步操作有了終極解決方案。No more callback hell。async 函數是 Generator 函數的語法糖。使用 關鍵字 async 來表示,在函數內部使用 await 來表示異步。想較于 G...
閱讀 1171·2021-10-15 09:39
閱讀 3071·2021-09-10 10:50
閱讀 3465·2019-08-30 15:53
閱讀 1890·2019-08-30 15:52
閱讀 2579·2019-08-29 15:31
閱讀 1986·2019-08-26 13:43
閱讀 2606·2019-08-26 13:37
閱讀 1450·2019-08-23 18:31