使用Promise
基本用法
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Promise是表示異步操作最終完成或失敗的對象。
Promise 本質(zhì)上是一個綁定了回調(diào)函數(shù)的對象。不同于我們平時看到的將回調(diào)作為參數(shù)傳遞給一個函數(shù)的形式。
首先來看下,我們熟悉的將回調(diào)作為函數(shù)傳遞給函數(shù)的例子:
function successCallback(result) {
console.log("Audio file ready at URL: " + result);
}
function failureCallback(error) {
console.log("Error generating audio file: " + error);
}
createAudioFileAsync(audioSettings, successCallback, failureCallback);
上面這個函數(shù)作用是異步的創(chuàng)建一個音頻文件碘橘,第一個參數(shù)是配置對象,第二和第三個分別是創(chuàng)建音頻文件成功和失敗后的回調(diào)函數(shù)脖旱。
那如果將上面的例子用Promise來處理久窟,會是怎樣的形式呢东抹?
-
Promise用then來表示處理結(jié)果
createAudioFileAsync(audioSettings).then(successfulCallback, failureCallback)
-
也可以寫成另外一種簡化形式
createAudioFileAsync(audioSettings).then(successfulCallback).catch(failureCallback)
我個人喜歡第二種形式蚂子,可讀性更強一些
Chain 鏈式調(diào)用
這個是Promise一個特別有意思的用法,對于處理多個串行的異步調(diào)用非常好用缭黔。
考慮這樣的場景食茎,我們要處理多個異步調(diào)用,每一個異步的調(diào)用都要利用前一個的結(jié)果试浙。那么就可以使用promise的chain董瞻。
例如,我們要獲取某些資源田巴,并且對資源處理之后钠糊,發(fā)送PUT請求更新資源的結(jié)果。而第一步是驗證用戶的合法性壹哺,是否具有這樣操作的權(quán)限抄伍。那么用Promise就可以寫成如下形式:
LoginPromise(userProfile)
.then(resp => {
if (resp.status == OK) return resp.authToken;
})
.then(token => {
return axios.get(url, {authToken: token});
})
.then(resp => {
let resource = prepareResource();
return axios.post(url, {resource: resource}, header);
})
.catch(err => {
console.log(err);
})
上述的代碼就會比之前嵌套的回調(diào)要優(yōu)雅而且表意很多。
當然管宵,catch之后依然還可以跟then截珍,表示出錯之后的繼續(xù)處理。
改造舊的異步調(diào)用
我們可能會碰到許多已經(jīng)存在的異步調(diào)用箩朴,如何使用Promise來改造他們呢岗喉,看一下下面這個例子:
setTimeout(() => saySomething("10 seconds passed"), 10000);
setTimeout
設(shè)置一個定時器,在10000ms之后發(fā)出消息炸庞。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
可以看到wait
只是作為一個Promise對象存在钱床,至于回調(diào)要做什么,在then中可以來設(shè)置埠居。
組合
Promise.resolve() 和 Promise.reject() 可以手動創(chuàng)建一個已經(jīng)resolve或者reject的Promise對象查牌。在某些場景下是一個非常有用的特性事期。
Promise.all() 和 Promise.race() 用來處理并行的Promise。
例如下面的代碼纸颜,就使用了Promise和reduce:
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
首先創(chuàng)建Promise.resolve()兽泣,然后依次處理func1和func2. 上述代碼等價于:
Promise.resolve().then(func1).then(func2);
以此類推,就可以將上述代碼擴展到多個函數(shù)的處理:
const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
composeAsync是一個接收可變參數(shù)作為一組回調(diào)函數(shù)的函數(shù)胁孙。使用方式如下:
const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
composeAsync 的返回值是一個函數(shù)唠倦,此函數(shù)輸入?yún)?shù)為x,執(zhí)行Promise.resolve(x).then(func1).then(func2).then(func3)...
transformData(data)
這個函數(shù)實現(xiàn)的功能是對data浊洞,由一組函數(shù)(func1, asyncFunc1, asyncFunc2, func2)來處理牵敷。
Nesting
這里需要注意的是Promise的Nesting,嵌套法希。如果你想實現(xiàn)的是Promise的串行調(diào)用,那么就一定使用Promise的Chain來操作靶瘸。
如果出現(xiàn)Promise的嵌套苫亦,就會出現(xiàn)一些隱藏的問題,看如下的例子:
promise1()
.then(res => {
promise2(res)
.then(newRes => {
})
.catch(err) {
console.log(err)
}
})
.then(result => {
promise3()
.then( () => {})
.catch(() => {})
})
.catch(err => {})
上述代碼中怨咪,也許你想在promise2出現(xiàn)失敗的情況下屋剑,后續(xù)的異步操作promise3不用再執(zhí)行,但實際上做不到诗眨,內(nèi)層的catch并不會傳遞到外層唉匾,所以then.promise3就會繼續(xù)執(zhí)行下去。
容易出的錯
// Bad example! Spot 3 mistakes!
doSomething().then(function(result) {
doSomethingElse(result) // Forgot to return promise from inner chain + unnecessary nesting
.then(newResult => doThirdThing(newResult));
}).then(() => doFourthThing());
// Forgot to terminate chain with a catch!
上面的代碼有三處錯誤:
- 內(nèi)層并沒有return新的promise匠楚,這樣就會到這外層不會等到內(nèi)層結(jié)束巍膘,而直接運行doFourthThing()。
- nesting 在前面已經(jīng)分析過了
- 沒有catch芋簿,在眾多瀏覽器中沒有catch會導(dǎo)致promise出錯
一個比較合理的promise例子如下:
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(newResult => doThirdThing(newResult))
.then(() => doFourthThing());
.catch(error => console.log(error));