Promise——從閱讀文件到簡單實現(一)
前言
最近幾周參加筆試面試,總是會遇到實現非同步和處理非同步的問題,然而作者每次都無法完美地回答。在最近一次筆試因為 Promise
而被刷掉後,我終於下定決心一個個地搞懂它們,就先拿 Promise
開刀吧 :)。
用法解析
ES6 的 Promise
物件是一個代理物件,被代理的值在 Promise
物件建立時可能是未知的,另外它允許你為非同步操作的成功和失敗分別繫結相應的處理方法。
Promise
常用於控制非同步操作的執行順序,而且可以讓非同步方法像同步方法那樣返回值。它不能立即取得非同步方法的返回值,但是它可以代理這個值,一旦非同步操作完成,就會以及將值傳遞給相應的處理方法。
一個 Promise
物件有以下幾種狀態:
pending fulfilled rejected
一個 Promise
物件的狀態可以從 pending
變成 fulfilled
,同時傳遞一個值給相應的 onfulfilled
處理方法;也可以從 pending
變成 rejected
,同時傳遞一個失敗資訊給相應的 onrejected
處理方法。
一旦一個 Promise
物件的狀態發生改變,就會觸發之前通過Promise.prototype. then
、 Promise.prototype. catch
和 Promise.prototype. finally
方法繫結的 onfulfilled
、 onrejected
和 onFinally
處理方法。
因為 then
、 catch
和 finally
方法都會返回一個新的 Promise
物件, 所以它們可以被鏈式呼叫。
建構函式
建構函式 Promise()
主要用來包裝還未支援 promises 的函式。
new Promise( function(resolve, reject) {...} /* executor */);
executor
executor
是帶有 resolve
和 reject
兩個函式引數的函式。 Promise
建構函式執行時立即呼叫 executor
函式,換句話說, executor
函式是在 Promise
建構函式內執行的,所以它是同步程式碼。在 executor
函式內呼叫 resolve
和 reject
函式,可以傳遞引數給相應的處理方法,並會分別將 promise
新建物件的狀態改為 fulfilled
(完成)或 rejected
(失敗)。
executor
內部通常會執行一些非同步操作如 ajax
,一旦完成,可以呼叫 resolve
函式傳遞引數並將 promise 物件的狀態改成 fulfilled,或者在發生錯誤時呼叫 reject
函式傳遞引數並將 promise 物件的狀態改成 rejected。如下:
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(); }); };
如果在 executor
函式中丟擲一個錯誤,那麼該 promise 物件狀態將變為 rejected
,並將錯誤作為引數傳遞給對應的 onrejected
處理方法。如下:
function myAsyncFunction(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onerror = () => { throw xhr.statusText; //throw xhr.statusText 效果等同於 reject(xhr.statusText) }; xhr.send(); }); };
靜態方法
靜態方法是定義在建構函式上的方法,宣告靜態方法:
Func.fn = function() {}
呼叫靜態方法:
Func.fn(args);
ES6
中的 Promise
建構函式有4個靜態方法:
resolve reject all race
Promise. resolve
(value):
返回一個由引數 value
解析而來的 Promise
物件。
如果 value
是一個 thenable
物件(帶有 then 方法的物件),返回的 Promise
物件會跟隨這個 thenable
物件,狀態隨之變化;如果傳入的 value
本身就是 Promise
物件,則直接返回 value
;其它情況下返回的 Promise
物件狀態為 fulfilled
,並且將該 value
作為引數傳遞給 onfulfilled
處理方法。
通常而言,如果你不知道一個值是否為 Promise
物件,就可以使用 Promise.resolve(value) 來將 value
以 Promise
物件的形式使用。
// resolve一個thenable物件 var p1 = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill("fulfilled!"); } }); console.log(p1 instanceof Promise) // true, 這是一個Promise物件 p1.then(function(v) { console.log(v); // 輸出"fulfilled!" }, function(e) { // 不會被呼叫 });
Promise. reject
(reason):
返回一個被給定原因 reason
拒絕的 Promise
物件。
返回的 Promise
物件的 status
狀態屬性為 rejected
, reason
拒絕原因屬性(傳遞給 onrejected
處理方法的 reason 引數)與引數 reason
相等。
Promise.reject(new Promise((resolve, reject) => resolve('done'))) .then(function(reason) { // 未被呼叫 }, function(reason) { console.log(reason); // new Promise });
Promise. all
(iterable):
引數: iterable
物件為 Array
物件、 Map
物件和 Set
物件等可迭代物件。
返回一個 Promise
物件。
如果 iterable
物件為空,Promise. all
會同步地返回一個狀態為 fulfilled
的 promise 物件。
如果 iterable
物件中的 promise 物件都變為 fulfilled
狀態,或者 iterable
物件內沒有 promise 物件,Promise. all
返回的 promise 物件將非同步地變為 fulfilled
狀態。
以上兩種情況返回的都是 fulfilled
狀態的 promise 物件,其 value
值(傳遞給 onfulfilled
處理方法的 value 引數)都是一個數組,這個陣列包含 iterable
物件中所有的基本值和 promise 物件 value
值。
如果 iterable
物件中任意一個 promise 物件狀態變為 rejected
,那麼Promise. all
就會非同步地返回一個狀態為 rejected
的 promise 物件,而且此 promise 物件的 reason
值(傳遞給 onrejected
處理方法的 reason 引數),等於 iterable
物件中狀態為 rejected
的那一個 promise 物件的 reason
值。
// this will be counted as if the iterable passed is empty, so it gets fulfilled var p = Promise.all([1,2,3]); // this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled var p2 = Promise.all([1,2,3, Promise.resolve(444)]); // this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected var p3 = Promise.all([1,2,3, Promise.reject(555)]); // using setTimeout we can execute code after the stack is empty setTimeout(function(){ console.log(p); console.log(p2); console.log(p3); }); // logs // Promise { <state>: "fulfilled", <value>: Array[3] } // Promise { <state>: "fulfilled", <value>: Array[4] } // Promise { <state>: "rejected", <reason>: 555 }
Promise. race
(iterable):
返回一個 Promise
物件。
一旦 iterable
中的某個 promise 物件完成或拒絕,返回的 promise 物件就會完成或拒絕,且返回的 promise 物件的 value
值(完成時)或 reason
值(拒絕時)和這個 promise 物件的 value
值(完成時)或 reason
值(拒絕時)相等。
var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, 'one'); }), promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then(function(value) { console.log(value); // Both resolve, but promise2 is faster }); // expected output: "two"
例項方法
例項方法是定義在原型物件上的方法,宣告例項方法:
Func.prototype.fn = function() {}
呼叫例項方法需要先建立一個例項:
let func = new Func(); func.fn(args);
Promise
的原型物件上有3個例項方法:
- Promise.prototype.
then
(onFulfilled
,onRejected
) - Promise.prototype.
catch
(onRejected
) - Promise.prototype.
finally
(onFinally
)
Promise.prototype. then
( onFulfilled
, onRejected
):
then
方法接收成功和失敗兩種情況的回撥函式作為引數,返回一個 Promise
物件。
引數: onFulfilled
和 onRejected
回撥函式
onFulfilled
:當 promise 物件變成 fulfilled 狀態時被呼叫。 onFulfilled
函式有一個引數,即 promise 物件完成時的 value
值。如果 onFulfilled
不是函式,它會在 then
方法內部被替換成一個 Identity
函式,即 (x) => (x) 。
onRejected
:當 promise 物件變成 rejected 狀態時被呼叫。 onRejected
函式有一個引數,即 promise 物件失敗時的 reason
值。如果 onRejected
不是函式,它會在 then
方法內部被替換成一個 Thrower
函式,即 (reason) => {throw reason} 。
返回:一旦呼叫 then
方法的 promise 物件被完成或拒絕,將非同步呼叫相應的處理函式( onFulfilled
或 onRejected
),即將處理函式加入 microtask
佇列中。如果 onFulfilled
或 onRejected
回撥函式:
- 返回一個值,則
then
返回的 promise 物件的status
變為fulfilled
,value
變為回撥函式的返回值; - 不返回任何內容,則
then
返回的 promise 物件的status
變為fulfilled
,value
變為undefined
; - 丟擲一個錯誤,則
then
返回的 promise 物件的status
變為rejected
,reason
變為回撥函式丟擲的錯誤; - 返回一個狀態為
fulfilled
的 promise 物件,則then
返回的 promise 物件的status
變為fulfilled
,value
等於回撥函式返回的 promise 物件的value
值; - 返回一個狀態為
rejected
的 promise 物件,則then
返回的 promise 物件的status
變為rejected
,reason
等於回撥函式返回的 promise 物件的reason
值; - 返回一個狀態為
pending
的 promise 物件,則then
返回的 promise 物件的status
變為pending
,且其status
將隨著回撥函式返回的 promise 物件的status
變化而變化,之後其value
或reason
值也會和此 promise 物件的value
或reason
值相同。
這裡提一下,這個地方看 MDN 文件中文翻譯實在看不懂,之後看英文原文反而稍微理解了,希望之後在實現 Promise 的過程中能理解更深。
var fromCallback; var fromThen = Promise.resolve('done') .then(function() { fromCallback = new Promise(function(){}); return fromCallback; }); setTimeout(function() { console.log(fromCallback);//fromCallback.status === 'pending' console.log(fromThen);//fromThen.status === 'pending' console.log(fromCallback === fromThen);//false }, 0);
Promise.prototype. catch
( onRejected
):
catch
方法接收失敗情況的回撥函式作為引數,返回一個 Promise
物件。
引數: onRejected
回撥函式,表現同 then
中的 onRejected
引數。
返回:promiseObj.catch(onRejected) 與 promiseObj.then(undefined, onRejected) 返回值相同。
Promise.resolve() .then( () => { // 返回一個 rejected promise throw 'Oh no!'; }) .catch( reason => { console.error( 'onRejected function called: ', reason ); }) .then( () => { console.log( "I am always called even if the prior then's promise rejects" ); });
Promise.prototype. finally
( onFinally
):
finally
方法接收 onFinally
回撥函式作為引數,返回一個 Promise
物件。
如果你想在 promise 執行完畢後,無論其結果是成功還是失敗,都做一些相同的處理時,可以使用 finally
方法。
引數: onFinally
回撥函式
onFinally
不接收任何引數,當 promise 物件變成 settled (fulfilled / rejected) 狀態時 onFinally
被呼叫。
返回:如果 onFinally
回撥函式
- 不返回任何內容或者返回一個值或者返回一個狀態為
fulfilled
的 promise 物件,則finally
返回的 promise 物件的status
、value
和reason
值與呼叫這個finally
方法的 promise 物件的值相同; - 丟擲一個錯誤或者返回一個狀態為
rejected
的 promise 物件,則finally
返回的 promise 物件的status
值變為rejected
,reason
值變為被丟擲的錯誤或者回調函式返回的 promise 物件的reason
值; - 返回一個狀態為
pending
的 promise 物件,則finally
返回的 promise 物件的status
值變為pending
,且其status
值將隨著回撥函式返回的 promise 物件的status
值變化而變化,之後其value
或reason
值也會和此 promise 物件的value
或reason
值相同。
Promise.reject('是我,開心嗎').finally(function() { var pro = new Promise(function(r){r('你得不到我')});//pro.status === 'fulfilled' return pro;//`onFinally`回撥函式返回一個狀態為`fulfilled`的 promise 物件 }).catch(function(reason) { console.log(reason); });
結語
將 MDN 文件整理了一下,加入了一些自己的理解,也花費了一天的時間。現在 Promise
各個方法的引數、返回值、功能和使用方法已經有個大概的瞭解了,為了進一步理解其原理,接下來我打算簡單地實現一下它。