Promise

前言

Promise通常是一種非同步式的語法結構,在ES6將其納入了語言標準,除了解決前一章提到的回調地獄的問題之外,也提供了更強大的功能。

Promise代理了一個非同步操作的Object,或者說是一個容器,裡面保存著未來最終返回的結果。

Promise state

一個Promise物件可處於以下幾種狀態:

  • pending:初始擱置狀態
  • fulfilled:表示操作成功完成
  • rejected:表示操作失敗

一旦狀態變化了,就不會再改變,類似於一種承諾。

以下是常被提到的一種狀態,他不處於pending

  • settled/resolved:可能是fulfilled或rejected

Promise

建立Promise

以下為一個Promise物件的建構方式,首先建立一個執行器函式(executor function),在此函式帶入兩個帶有callback性質的參數分別為resolvereject,被調用時即會將pending狀態轉換至fulfilled或rejected,調用方式如下:

  • 當操作成功時

主動調用resolve()並傳入合法的value,此時Promise狀態會轉移至fulfilled

  • 當操作失敗或例外時

主動調用reject()並傳入失敗的reason,此時Promise狀態會轉移至rejected

const myFirstPromise = new Promise((resolve, reject) => {
  // 執行一些非同步作業,最終呼叫:
	resolve(someValue); // 實現
  // 或
	reject("failure reason"); // 拒絕
});

以下是一個HTTP Request用Promise建構的範例

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
};

Promise.prototype.then方法

then方法是Promise主要的精神,其作用是為Promise instance添加狀態改變時的callback function,他會return一個promisethen function的第一個參數是resolved狀態的callback function,第二個參數(optional)是rejected狀態的callback function 。

promise.then(onFulfilled, onRejected)

使用範例

var p1 = new Promise( (resolve, reject) => {
  resolve('Success!');
  // or
  // reject ("Error!");
} );

p1.then( value => {
  console.log(value); // Success!
}, reason => {
  console.log(reason); // Error!
} );

錯誤處理

當函式拋出一個error或是回傳一個resolved狀態的Promise,then都會return一個resolved的Promise。

const promise = new Promise((resolve, reject) => {
	reject ("Error!");
	// or
	// throw new Error('test');
})

promise.then(null, reason => {
  console.log(reason.toString()); // Error!
});

以上的方式也可用Promise.prototype.catch()達到,此方法會return一個resolved狀態的新Promise,catch在處理 promise 組合的錯誤時很有幫助,一般較推薦此寫法。

.catch(reason => {
  console.log(reason); // Error!
});

鏈式調用(Chaining)

一個常見的需求是連續執行兩個或多個非同步操作,這樣的情形下每個操作會將其返回的結果帶到下一個操作執行,透過promise chain即可完成這種需求。

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

也可以箭頭函式(arrow functions)來縮減此寫法,如下所示

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

在一個失敗操作catch之後可以繼續使用鏈式操作,以幫助後續動作繼續完成。

new Promise((resolve, reject) => {
    console.log('Initial');
    resolve();
})
.then(() => {
    throw new Error('Something failed');
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});
  • 創建的Promise為fulfilled狀態,印出log
  • 第一個then拋出一個error,回傳新的Promise為rejected狀態,跳過輸出
  • catch接到rejected狀態,並回傳新的Promise為fulfilled狀態,印出log
  • 印出log

“Initial”

“Do that”

“Do this whatever happened before”

在error傳播在鏈式操作中時,一但前一個操作有拋出error,若之後沒有實作rejected狀態的callback function,則Promise就會不斷往後傳遞error,直到實作catch才會返回rejected的Promise,後續也能在接續其他操作。

doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

範例

new Promise((resolve, reject) => {
    reject();
})
.then(() => async2(), undefined)       
.then(() => async3(), undefined)
.then(undefined, (err) => errorHandler1())
.then(() => async4(), (err) => errorHandler2())
.then(undefined, (err) => console.log('Don\'t worry about it'))
.then(() => console.log('All done!'), undefined)
  • 創建的promise物件的狀態是rejected
  • async2不會被執行,新的Promise物件直接是rejected狀態
  • async3不會被執行,新的Promise物件直接是rejected狀態
  • errorHandler1()被執行,新的Promise物件為fulfilled狀態
  • async4()被執行,新的Promise物件為fulfilled狀態
  • 跳過輸出字串,新的Promise物件為fulfilled狀態,回傳值繼續傳遞
  • 輸出字串

“All done!”

靜態方法 (Static Methods)

Promise中共有四個 static method:

  • Promise.resolve(value)
  • Promise.reject(reason)
  • Promise.all(iterable)
  • Promise.race(iterable)

resolve & reject

Promise.resolve與 Promise.reject 會直接產生一個fulfilled或rejected的promise。

Promise.resolve()
  .then(value => console.log('Immediate resolved!'))

Promise.reject(new Error('Error!!!')).
  .then(() => { /* will not go here */ })
  .catch(err => console.log(`Error shows up here ${err}`))

all & race

Promise.all 與 Promise.race皆可傳入一個iterable的參數,將多個傳入的Promise instance包裝成一個新的Promise回傳,其狀態改變有不同的結果。

  • Promise.all
    • fulfill:所有傳入的Promises皆 fulfill。Resolved value為與傳入的Promises相同順序value array
    • reject:任一個Promise被reject即reject。Error reason為與第一個被reject的Promise相同
  • Promise.race
    • 任何一個Promise被fulfill或reject,皆回傳此狀態的promise
const promise = Promise.all([p1, p2, p3]);
// or
const promise = Promise.race([p1, p2, p3]);

Ref