ES6提供的Promise對(duì)象是異步控制相較于回調(diào)的更好的一種方法。包括ES8提供的asyncFunction本質(zhì)上也是基于Promise和生成器的結(jié)合,因此在已經(jīng)了解Promise對(duì)象的常用API基礎(chǔ)上,更加深入的去了解如何使用Promise去解決一些常見(jiàn)的難題對(duì)于開(kāi)發(fā)將會(huì)有一些幫助。
文章閱讀前,希望讀者已經(jīng)對(duì)Promise的使用及相關(guān)api有了一定的了解 。[ 快速學(xué)習(xí)Promise ]
一、如何對(duì)Promise異步任務(wù)進(jìn)行超時(shí)監(jiān)聽(tīng)
任務(wù)描述
作為一道開(kāi)胃小菜,這個(gè)問(wèn)題還是不難解決的。平時(shí)對(duì)于一些異步任務(wù)很有可能需要進(jìn)行超時(shí)監(jiān)聽(tīng),那么如何利用Promise來(lái)進(jìn)行超時(shí)監(jiān)聽(tīng)/監(jiān)控呢?
解決方案
// 封裝一個(gè)嚴(yán)格任務(wù)函數(shù),第一個(gè)參數(shù)為promise對(duì)象,第二個(gè)參數(shù)為判定的超時(shí)標(biāo)準(zhǔn),默認(rèn)3s
function strictTack(promise,delay = 3){
// 函數(shù)返回一個(gè)超時(shí)基準(zhǔn)promise對(duì)象
let promiseTimeout = function(delay){
return new Promise((res,rej)=>{
setTimeout(()=>{
rej(new Error("運(yùn)行超時(shí)!"))
},1000 * delay)
})
}
// race,參數(shù)數(shù)組內(nèi)的promise并發(fā)執(zhí)行,一旦其中有一個(gè)promise對(duì)象產(chǎn)生判決就會(huì)終止其余的promise對(duì)象
return Promise.race([promise,promiseTimeout(delay)])
}
// 異步任務(wù)p1
let p1 = new Promise((res,rej)=>{
setTimeout(()=>{
res("p1 was resoved")
},1000 * 2)
})
// 異步任務(wù)p2
let p2 = new Promise((res,rej)=>{
setTimeout(()=>{
res("p2 was resoved")
},1000 * 4)
})
strictTack(p1)
.then(e=>{
console.log(e)// p1 was resoved
}).catch(err=>{
console.log(err)
})
strictTack(p2)
.then(e=>{
console.log(e)
}).catch(err=>{
console.log(err) // Error:運(yùn)行超時(shí)!
})
任務(wù)總結(jié)
對(duì)于這個(gè)問(wèn)題來(lái)說(shuō),核心部分就是利用好Promise.race這個(gè)API,因?yàn)槭褂盟鶊?zhí)行的的Promise對(duì)象中的異步任務(wù)都是“競(jìng)態(tài)”的,只接受第一個(gè)發(fā)生判決的Promise對(duì)象。那么此時(shí)采用超時(shí)基準(zhǔn)promise對(duì)象配合race正好巧妙的解決了超時(shí)監(jiān)聽(tīng)的問(wèn)題。
二、如何自己實(shí)現(xiàn)一個(gè)Promise
任務(wù)描述
這種具有一定難bian度tai的題目一般會(huì)在面試中遇到,那么如何手動(dòng)實(shí)現(xiàn)一個(gè)Promise(或部分功能)?
解決方案
-
V 0.1初步版本的Promise (不支持正規(guī)Promise的鏈?zhǔn)秸{(diào)用)class PromisePolyfill{ constructor(exector = ()=>{}){ this.status = 'pending' // promise當(dāng)前狀態(tài) this.reason = undefined // 用戶回顯到reject函數(shù)的值 this.value = undefined // 用戶回顯到resolve函數(shù)的值 // 成功事件回調(diào)隊(duì)列 this.onFulfilledCallBacks = [] // 失敗事件回調(diào)隊(duì)列 this.onRejectedCallBacks = [] //實(shí)現(xiàn)resolve函數(shù) let resolve = (value)=>{ if(this.status === 'pending'){ this.value = value this.status = 'fulfilled' this.onFulfilledCallBacks.map(e=>e()) } } // 實(shí)現(xiàn)reject函數(shù) let reject = (reason)=>{ if(this.status === 'pending'){ this.reason = reason this.status = 'rejected' this.onRejectedCallBacks.map(e=>e()) } } try{ // 運(yùn)行執(zhí)行器 exector(resolve,reject) }catch(err){ reject(err) } } then(onFulfilled,onRejected){ // 同步情況 if(this.status === 'fulfilled' && onFulfilled){ onFulfilled(this.value) } if(this.status === 'rejected' && onRejected){ onRejected(this.reason) } // 異步情況 if(this.status === 'pending'){ // 將任務(wù)成功回調(diào)加入到成功隊(duì)列中 onFulfilled && this.onFulfilledCallBacks.push(()=>{ onFulfilled(this.value) }) // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中 onRejected && this.onRejectedCallBacks.push(()=>{ onRejected(this.reason) }) } return this } catch(onRejected){ // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中 onRejected && this.onRejectedCallBacks.push(()=>{ onRejected(this.reason) }) return this } } -
V 0.2支持鏈?zhǔn)秸{(diào)用,但是未實(shí)現(xiàn)Promise的相關(guān)API--all、race、reject、resolveclass PromisePolyfill{ constructor(exector = ()=>{}){ this.status = 'pending' // promise當(dāng)前狀態(tài) this.reason = undefined // 用戶回顯到reject函數(shù)的值 this.value = undefined // 用戶回顯到resolve函數(shù)的值 // 成功事件回調(diào)隊(duì)列 this.onFulfilledCallBacks = [] // 失敗事件回調(diào)隊(duì)列 this.onRejectedCallBacks = [] //實(shí)現(xiàn)resolve函數(shù) let resolve = (value)=>{ if(this.status === 'pending'){ this.value = value this.status = 'fulfilled' this.onFulfilledCallBacks.map(e=>e()) } } // 實(shí)現(xiàn)reject函數(shù) let reject = (reason)=>{ if(this.status === 'pending'){ this.reason = reason this.status = 'rejected' this.onRejectedCallBacks.map(e=>e()) } } try{ // 運(yùn)行執(zhí)行器 exector(resolve,reject) }catch(err){ reject(err) } } then(onFulfilled,onRejected){ let promise2 = new PromisePolyfill((resolve, reject)=>{ if(this.status === 'fulfilled' && onFulfilled){ let x = onFulfilled(this.value) // resolvePromise函數(shù),處理自己return的promise和默認(rèn)的promise2的關(guān)系 resolvePromise(promise2, x, resolve, reject) } if(this.status === 'rejected' && onRejected){ let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) } // 異步情況 if(this.status === 'pending'){ // 將任務(wù)成功回調(diào)加入到成功隊(duì)列中 onFulfilled && this.onFulfilledCallBacks.push(()=>{ let x = onFulfilled(this.value) // resolvePromise函數(shù),處理自己return的promise和默認(rèn)的promise2的關(guān)系 resolvePromise(promise2, x, resolve, reject) }) // 將任務(wù)失敗回調(diào)加入到失敗隊(duì)列中 onRejected && this.onRejectedCallBacks.push(()=>{ let x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) }) } }) return promise2 } } function resolvePromise(promise2, x, resolve, reject){ // 循環(huán)引用報(bào)錯(cuò) if(x === promise2){ // reject報(bào)錯(cuò) return reject(new TypeError('Chaining cycle detected for promise')); } // 防止多次調(diào)用 let called; // x不是null 且x是對(duì)象或者函數(shù) if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+規(guī)定,聲明then = x的then方法 let then = x.then; // 如果then是函數(shù),就默認(rèn)是promise了 if (typeof then === 'function') { // 就讓then執(zhí)行 第一個(gè)參數(shù)是this 后面是成功的回調(diào) 和 失敗的回調(diào) then.call(x, y => { // 成功和失敗只能調(diào)用一個(gè) if (called) return; called = true; // resolve的結(jié)果依舊是promise 那就繼續(xù)解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能調(diào)用一個(gè) if (called) return; called = true; reject(err);// 失敗了就失敗了 }) } else { resolve(x); // 直接成功即可 } } catch (e) { // 也屬于失敗 if (called) return; called = true; // 取then出錯(cuò)了那就不要在繼續(xù)執(zhí)行了 reject(e); } } else { resolve(x); } } -
在
v0.2基礎(chǔ)上封裝相關(guān)API-
all(iterable)這個(gè)方法接受一個(gè)promise數(shù)組,返回一個(gè)新的promise對(duì)象,該promise對(duì)象在iterable參數(shù)對(duì)象里所有的promise對(duì)象都成功的時(shí)候才會(huì)觸發(fā)成功,一旦有任何一個(gè)iterable里面的promise對(duì)象失敗則立即觸發(fā)該promise對(duì)象的失敗。
PromisePolyfill.all = function (promises){ function processData (){ let Arr = [] let i = 0 return function (index,data,resolve){ Arr[index] = data i++ if(i === promises.length){ resolve(Arr) } } } let process = processData() return new PromisePolyfill((resolve,reject)=>{ promises.map((promise,index) => { promise.then(data=>{ process(index,data,resolve) },reject) }) }) }-
race(iterable)這個(gè)方法接受一個(gè)promise數(shù)組。當(dāng)iterable參數(shù)里的任意一個(gè)子promise被成功或失敗后,父promise馬上也會(huì)用子promise的成功返回值或失敗詳情作為參數(shù)調(diào)用父promise綁定的相應(yīng)句柄,并返回該promise對(duì)象。
PromisePolyfill.race = function(promises){ return new PromisePolyfill((resolve,reject)=>{ promises.map(promise=>{ promise.then(resolve,reject) }) }) }-
resolve(value)返回一個(gè)狀態(tài)由給定value決定的Promise對(duì)象。如果該值是thenable(即,帶有then方法的對(duì)象),返回的Promise對(duì)象的最終狀態(tài)由then方法執(zhí)行決定;否則的話(該value為空,基本類型或者不帶then方法的對(duì)象),返回的Promise對(duì)象狀態(tài)為fulfilled,并且將該value傳遞給對(duì)應(yīng)的then方法。
PromisePolyfill.resolve = function(data){ return new PromisePolyfill((resolve,reject)=>{ resolve(data) }) }-
reject(value)返回一個(gè)狀態(tài)為失敗的Promise對(duì)象,并將給定的失敗信息傳遞給對(duì)應(yīng)的處理方法
PromisePolyfill.reject = function(data){ return new PromisePolyfill((resolve,reject)=>{ reject(data) }) } -
三、手寫(xiě)一個(gè)Promise的Ajax函數(shù)
任務(wù)描述
老生常談的問(wèn)題了,面試官喜歡問(wèn)的問(wèn)題。主要思想實(shí)現(xiàn)出來(lái)就好了
解決方案
function ajax (params){
return new Promise((resolve,reject)=>{
let request = new XMLHttpRequset()
//設(shè)置請(qǐng)求方式和請(qǐng)求地址
request.open(params.type || 'get',params.url)
request.onreadystatechange = ()=>{
//當(dāng)請(qǐng)求返回響應(yīng)時(shí)
if(request.readyState === 4){
// 默認(rèn)情況下返回200意味著請(qǐng)求成功
if(request.status === 200){
resolve(JSON.parse(request.responseText))
}else{
reject(JSON.parse(request.responseText))
}
}
}
//攜帶請(qǐng)求頭發(fā)送請(qǐng)求
request.send(params.data || null)
})
}
四、理論部分
關(guān)于then和catch的返回值
如果自己去實(shí)現(xiàn)了一次Promise對(duì)象之后會(huì)發(fā)現(xiàn),then和catch如果沒(méi)有返回一個(gè)標(biāo)準(zhǔn)的promise對(duì)象時(shí),將會(huì)默認(rèn)返回一個(gè)Promise.resolve(基本值),即使你沒(méi)有寫(xiě)任何return語(yǔ)句。
let p1 = new Promise((res,rej)=>{
setTimeout(()=>{
// rej("p1 was rejected")
res("p1 was resoved")
},1000 * 2)
})
p1.then(res=>{
console.log("Then1:"+res)
}).then(res=>{
console.log("Then2:"+res)
return 'Hello'
}).catch(err=>{
console.log("Error1:"+err)
return Promise.reject("Error For Catch 1")
}).then(res=>{
console.log("Then3:"+res)
return Promise.reject('ERROR For Then 3')
}).then(res=>{
console.log("Then4:"+res)
}).catch(err=>{
console.log("Error2:"+err)
}).then(res=>{
console.log("Then5:"+res)
})
/*
結(jié)果
Then1:p1 was rejected
Then2:undefined
Then3:Hello
Error2:ERROR For Then 3
Then5:undefined
*/
經(jīng)過(guò)分析不難發(fā)現(xiàn),對(duì)于一個(gè)Promise中的鏈?zhǔn)秸{(diào)用的順序是這樣的:
- 對(duì)于一個(gè)
then/catch,默認(rèn)返回為一個(gè)resolve - 當(dāng)返回一個(gè)
reject時(shí),將調(diào)用當(dāng)前節(jié)點(diǎn)往后的最近的catch節(jié)點(diǎn)中的回調(diào)(跳過(guò)中間的then) - 如果當(dāng)前節(jié)點(diǎn)并沒(méi)有返回
reject但是恰巧后面的節(jié)點(diǎn)就是是catch,那么將會(huì)跳過(guò)這個(gè)(或連續(xù)幾個(gè))catch,直接到后面最近的then節(jié)點(diǎn)
以上為現(xiàn)象,原理請(qǐng)參考第二節(jié)Promise的實(shí)現(xiàn)
關(guān)于async Function (async/await)
asyncFunction是ES8中提出的更好的異步解決方案,實(shí)際用起來(lái)也確實(shí)如此。那么asyncFunction與Promise之間存在什么樣的關(guān)系呢?
[ 快速學(xué)習(xí)async Function ]
async function fetch(){
console.log("Fetch is RUNING") // 立即輸出Fetch is RUNING
let data1 = await new Promise((res,rej)=>{
setTimeout(()=>{
res(1)
},1000 * 2)
})
console.log(data1) //時(shí)間線至少2s后打印1
let data2 = await new Promise((res,rej)=>{
setTimeout(()=>{
res(2)
},1000 * 5)
})
console.log(data2) // 時(shí)間線至少2+5=7s后打印2
console.log(data1+data2) // 緊接著上一句執(zhí)行,打印 3
}
fetch()
上例中展示了async/await的基本用法和執(zhí)行過(guò)程
- 第一句同步打印任務(wù)可以直接輸出。
- 第二句遇到
await,將會(huì)等待當(dāng)前await任務(wù)返回Promise.resolve后才會(huì)繼續(xù)執(zhí)行下一句。這種模式和生成器的模式非常相像,async/await優(yōu)于生成器的地方就是,生成器需要手動(dòng)next才能進(jìn)行下一步,而async/await是自動(dòng)next,顯然實(shí)現(xiàn)上要更為復(fù)雜。 - 依次類推...
從這個(gè)例子中可以看出,多個(gè)await產(chǎn)生的異步任務(wù)是逐個(gè)執(zhí)行的,而不是并發(fā)。所以想要實(shí)現(xiàn)多異步任務(wù)并發(fā)控制,仍然需要使用
Promise.all或者Promise.race。
接下來(lái),再來(lái)看個(gè)例子
async function fetch(){
let data1 = await new Promise((res,rej)=>{
setTimeout(()=>{
res(1)
},1000 * 2)
})
let data2 = await new Promise((res,rej)=>{
setTimeout(()=>{
res(2)
},1000 * 5)
})
return data1 + data2
}
fetch().then(res=>{
console.log(res) // 至少7s后輸出 3
})
你沒(méi)看錯(cuò),asyncFunction將會(huì)默認(rèn)返回一個(gè)Promise對(duì)象,和Promise.prototype.then返回Promise的效果一致
到這兒不難看出Promise的重要性了吧,現(xiàn)在的主要異步任務(wù)控制方式實(shí)際上都沒(méi)有離開(kāi)Promise。