Promise && async/await的理解和用法
為什麼需要promise(承諾)這個東西
在之前我們處理非同步函式都是用回撥這個方法,回撥巢狀的時候會發現 閱讀性 和 除錯 的難度會增加很多;
怎麼理解promise
想象一下,你把一個任務交給一個不錯的小夥子,他叫承諾;不用擔心你交給他的任務會丟失,他總會返回的,做成功了resolve,失敗了reject;
var promise = new Promise((resolve, reject) =>{
//交給給“承諾”同學一個非同步任務
setTimeout(()=>{
if(true){
// 成功了,返回params
resolve(`params`)
}else{
// 失敗了,返回error
reject(`error`)
}
}, 1000)
})
// 上面是給承諾一個任務,下面是"承諾"同學的返回
promise.then((res)=>{
console.log(res)
}).catch((rej)=>{
console.log(res)
})
怎麼使用promise
實際情況中,非同步的場景沒有那麼簡單,你可以會遇到下面這些場景
- “序列應用場景”下的處理方案
let promise = new Promise((res, rej)=>{
asyncFunc(`promise`, 1000, res, rej)
})
promise.then(res=>{
console.log(res);
return new Promise((res, rej)=>{
asyncFunc(`second`, 2000, res, rej)
})
}).then(res=>{
console.log(res);
return new Promise((res, rej)=>{
asyncFunc(`third`, 1000, res, rej)
})
// throw `oh, no!`;
}).then(res=>{
console.log(res);
console.log(`endinggggggg`)
}).catch(err=>{
console.log(`catch`, err)
})
- “並行應用場景”的處理方案(即在所有的非同步操作完成之後執行)
let promise1 = new Promise((res, rej)=>{
asyncFunc(`promise1`, 1000, res, rej)
})
let promise2 = new Promise((res, rej)=>{
asyncFunc(`promise2`, 2000, res, rej)
})
let promise3 = new Promise((res, rej)=>{
asyncFunc(`promise3`, 1000, res, rej)
})
var promiseAll = Promise.all([promise1, promise2, promise3])
promiseAll.then(res =>{
console.log(`最終的結果`, res)
}).catch(err =>{
console.log(`catch`, err);
})
- “競速模式下”,如字面意思,只要是哪一個提前完成了。就表示整個狀態處理完成狀態;這個場景可以發散成如果是超過了3s我就不去做這件事情了
let promise1 = new Promise((res, rej)=>{
asyncFunc(`promise1`, 1000, res, rej, true)
})
let promise2 = new Promise((res, rej)=>{
asyncFunc(`promise2`, 2000, res, rej, true)
})
let promise3 = new Promise((res, rej)=>{
asyncFunc(`promise3`, 1000, res, rej)
})
// 1000s的任務完成了,就直接返回promise1了
var promiseRace = Promise.race([promise1, promise2, promise3])
promiseRace.then(res =>{
console.log(`最終的結果`, res)
}).catch(err =>{
console.log(`catch`, err);
})
js是單執行緒,promise,setTimeout的執行優先順序
講這一塊的東西就得講講nodejs的事件處理機制;
事件佇列應該是一個資料結構,所有的事情都被事件迴圈排隊和處理,直到佇列為空。但是Node中的這種情況與抽象反應器模式如何描述完全不同。
下面講的東西只適合V8;
NodeJS中有許多佇列,其中不同型別的事件在自己的佇列中排隊。
在處理一個階段之後並且在移到下一個佇列之前,事件迴圈將處理兩個中間佇列,直到中間佇列中沒有剩餘的專案。
定義:
有四種主要型別,由libuv事件迴圈處理;
- 過期的定時器和間隔佇列 – (比如使用setTimeout,setInterval);
- IO事件佇列 – 已完成的IO事件
- Immediates佇列 – 使用setImmediate功能新增回撥
- 關閉處理程式佇列 – 任何close事件處理程式
還有2箇中間佇列,不屬於libuv本身的一部分,但是是nodejs的一部分;
- Next Ticks Queue – 使用process.nextTick 函式新增回撥;(優先順序更高)
- 其他微型任務佇列 – 包括其他微型任務,例如已經解決的承諾回撥;
如何工作的:
上圖是node中libuv模組在處理非同步I/O操作的流程圖;
Node通過定時器檢查佇列中的任何過期定時器來啟動事件迴圈,並在每一個步驟中遍歷每一個佇列。如果沒有任務則迴圈退出,每一次佇列處理都被視為事件迴圈的一個階段。特別有意思的是中間紅色的佇列,每次階段都會優先去處理中間佇列的任務。然後再去處理其他的佇列。
什麼是async/await
async/await 可以是Generator和promise結合實現的;
注意核心點:
- asnyc 函式總是返回一個Promise物件,不論函式是否return Promise;
- await 後面跟著Promise物件,如果不是Promise物件,也會被封裝成Promise;
- async/await 和Promise物件在本質上是一樣的
其他note點
- await的任何內容都通過Promise.resolve()傳遞,這樣就可以安全的await非原生Promise;
- 建構函式以及getter/settings方法不能是非同步的;
- 儘管編寫的是同步的程式碼,但是也不要錯失並行執行的機會,不然你需要消耗等待的效能喪失;
- Babel REPL 說起來很有趣。試試就知道。
怎麼用async/await
實際情況中,非同步的場景沒有那麼簡單,你可以會遇到下面這些場景
- 場景:只有一個await並且 resolve
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return `done`
}
f().then(v=> console.log(v));
- 場景:只有一個await並且 reject
let a;
async function g(){
await Promise.reject(`error`);
a = await 1;
}
g().then(v=>console.log(v)).catch(err=>console.log(err));
- 場景:有多個await, 可以用try/catch
let a ;
async function g(){
try{
await Promise.reject(`error`)
}catch(err){
console.log(err)
}
a= await 1;
return a;
}
g().then(v=>console.log(v)).catch(err=>console.log(err));
- 場景:等待平行任務
async function series(){
const await1 = delay(1000);
const await2 = delay(1000);
await await1;
await await2;
return `done`
}
series();
歡迎提意見和star
如果有不對的可以提issue
如果覺得對你有幫助可以star下
github
參考文件
- 這一系列的文件講的很不錯
https://juejin.im/post/5b777f…
- 講promise,setTimeout優先順序的;nodejs中事件迴圈中的任務優先順序
https://jsblog.insiderattack….
- developers.google.com域名下面的文件還是很有質量的,其中會比較全面的介紹怎麼去用promise和async/await