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