Promise 的使用
使用Promise
基本用法
A Promise is an object representing the eventual completion or failure of an asynchronous operation. Promise是表示非同步操作最終完成或失敗的物件。
Promise 本質上是一個綁定了回撥函式的物件。不同於我們平時看到的將回調作為引數傳遞給一個函式的形式。
首先來看下,我們熟悉的將回調作為函式傳遞給函式的例子:
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);
上面這個函式作用是非同步的建立一個音訊檔案,第一個引數是配置物件,第二和第三個分別是建立音訊檔案成功和失敗後的回撥函式。
那如果將上面的例子用Promise來處理,會是怎樣的形式呢?
-
Promise用then來表示處理結果
createAudioFileAsync(audioSettings).then(successfulCallback, failureCallback)
-
也可以寫成另外一種簡化形式
createAudioFileAsync(audioSettings).then(successfulCallback).catch(failureCallback)
我個人喜歡第二種形式,可讀性更強一些
Chain 鏈式呼叫
這個是Promise一個特別有意思的用法,對於處理多個序列的非同步呼叫非常好用。
考慮這樣的場景,我們要處理多個非同步呼叫,每一個非同步的呼叫都要利用前一個的結果。那麼就可以使用promise的chain。
例如,我們要獲取某些資源,並且對資源處理之後,傳送PUT請求更新資源的結果。而第一步是驗證使用者的合法性,是否具有這樣操作的許可權。那麼用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); })
上述的程式碼就會比之前巢狀的回撥要優雅而且表意很多。
當然,catch之後依然還可以跟then,表示出錯之後的繼續處理。
改造舊的非同步呼叫
我們可能會碰到許多已經存在的非同步呼叫,如何使用Promise來改造他們呢,看一下下面這個例子:
setTimeout(() => saySomething("10 seconds passed"), 10000);
setTimeout
設定一個定時器,在10000ms之後發出訊息。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
可以看到wait
只是作為一個Promise物件存在,至於回撥要做什麼,在then中可以來設定。
組合
Promise.resolve() 和 Promise.reject() 可以手動建立一個已經resolve或者reject的Promise物件。在某些場景下是一個非常有用的特性。
Promise.all() 和 Promise.race() 用來處理並行的Promise。
例如下面的程式碼,就使用了Promise和reduce:
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
首先建立Promise.resolve(),然後依次處理func1和func2. 上述程式碼等價於:
Promise.resolve().then(func1).then(func2);
以此類推,就可以將上述程式碼擴充套件到多個函式的處理:
const applyAsync = (acc,val) => acc.then(val); const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
composeAsync是一個接收可變引數作為一組回撥函式的函式。使用方式如下:
const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
composeAsync 的返回值是一個函式,此函式輸入引數為x,執行Promise.resolve(x).then(func1).then(func2).then(func3)...
transformData(data)
這個函式實現的功能是對data,由一組函式(func1, asyncFunc1, asyncFunc2, func2)來處理。
Nesting
這裡需要注意的是Promise的Nesting,巢狀。如果你想實現的是Promise的序列呼叫,那麼就一定使用Promise的Chain來操作。
如果出現Promise的巢狀,就會出現一些隱藏的問題,看如下的例子:
promise1() .then(res => { promise2(res) .then(newRes => { }) .catch(err) { console.log(err) } }) .then(result => { promise3() .then( () => {}) .catch(() => {}) }) .catch(err => {})
上述程式碼中,也許你想在promise2出現失敗的情況下,後續的非同步操作promise3不用再執行,但實際上做不到,內層的catch並不會傳遞到外層,所以then.promise3就會繼續執行下去。
容易出的錯
// 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!
上面的程式碼有三處錯誤:
- 內層並沒有return新的promise,這樣就會到這外層不會等到內層結束,而直接執行doFourthThing()。
- nesting 在前面已經分析過了
- 沒有catch,在眾多瀏覽器中沒有catch會導致promise出錯
一個比較合理的promise例子如下:
doSomething() .then(function(result) { return doSomethingElse(result); }) .then(newResult => doThirdThing(newResult)) .then(() => doFourthThing()); .catch(error => console.log(error));