ES6 Promise 物件
在 MDN 中對 Promise 的定義是:Promise 物件用於表示一個非同步操作的最終狀態(完成或失敗),以及其返回的值。
Promise 物件存在以下三種狀態:
- pending: 初始狀態;
- fulfilled:成功狀態;
- rejected:失敗狀態;
Promise 物件的初始狀態是 pending,最終狀態是 fulfilled 或者 rejected,其中 fulfilled 表示成功狀態,rejected 表示失敗狀態。狀態只能夠由 pending 變成 fulfilled 或 rejected,當 Promise 物件的狀態改變後就不可以再改變了。

Promise 狀態改變
Promise 簡單的使用方式如下
let pro = new Promise((resolve, reject) => { // 執行非同步操作程式碼 // 非同步操作成功,則 resolve(value) // 非同步操作失敗,則 reject(error) });
從上面的使用方式來看,我們可以知道 Promise 是一個建構函式,其引數接受一個匿名函式,該匿名函式有兩個引數,分別是 resolve 和 reject 函式。
通過 new Promise 得到一個 Promise 物件,Promise 建構函式所接受的匿名函式會直接執行,一般是在裡面執行非同步操作。
當非同步操作成功的時候,執行 resolve 函式,會把 Promise 物件的狀態由 pending 變為 fulfilled;當非同步操作失敗的時候,執行 reject 函式,會把 Promise 物件的狀態由 pending 變為 rejected。其中 resolve 和 reject 函式都分別傳入了返回值(value)和錯誤值(error),這兩個值將會作為 Promise 物件 then 方法的兩個回撥函式的實參。
Promise 物件上具有很多方法,其方法是掛載在對應的原型即 Promise.prototype 上面的,下面就 Promise 物件的方法做一個詳細的介紹。
Promise.prototype.then(onFulfilled, onRejected)
當 Promise 物件的狀態由 pending 變為 fulfilled 或 rejected 的時候,會觸發 Promise 物件的 then 方法。
pro.then((value) => { // fulfilled }, (error) => { // rejected });
then 方法接受兩個回撥函式作為引數,當 Promise 物件當狀態由 pending 變為 fulfilled 的時候,執行第一個回撥函式;當 Promise 物件當狀態由 pending 變為 rejected 的時候,執行第二個回撥函式(可選)。這兩個回撥函式的引數分別是執行 resolve 和 reject 函式時傳入的值。
舉個簡單的例子
let timeoutPro = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'resolve'); }); timeoutPro.then((value) => { console.log(value); });
我們還可以將其封裝起來
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'resolve'); }); } timeout(1000).then((value) => { console.log(value); });
下面是一個用 Promise物件實現的 Ajax 操作的例子
let get = function (url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState != 4) return; if (xhr.status == 200) { resolve(xhr.response); } else { reject(xhr.statusText); } }; }); }; get("0010.json").then((res) => { console.log(res.name);// ttsy }, (error) => { console.log(error); });
注:這裡 0010.json 為一個與存放上述程式碼的指令碼檔案同目錄的一個 json 檔案,裡面的內容如下
{ "name":"ttsy", "age":25 }
then 方法返回的是一個新的 Promise 物件。因此可以採用鏈式寫法,即 then 方法後面再呼叫另一個 then 方法。而 下一個 then 方法的行為跟前一個 then 方法的返回值有關 。
- 如果上一個 then 中的回撥函式返回一個值,那麼該 then 返回的 Promise 物件將會成為 fulfilled 狀態,並且上一個 then 的回撥函式返回的值將作為下一個 then 的回撥函式的引數值;
- 如果上一個 then 中的回撥函式丟擲一個錯誤,那麼該 then 返回的 Promise 物件將會成為 rejected 狀態,並且上一個 then 的回撥函式丟擲的錯誤將作為下一個 then 的回撥函式的引數值;
- 如果上一個 then 中的回撥函式返回一個 Promise 物件,那麼下一個 then 則會根據返回的 Promise 物件的狀態變化來執行。
舉個例子~
當上一個 then 中的回撥函式返回一個值時
timeout(1000).then((value) => { return value; }).then((value) => { console.log(value);// resolve })
當上一個 then 中的回撥函式丟擲一個錯誤時
timeout(1000).then((value) => { throw 'error' }).then((value) => { console.log(value); }, (error) => { console.log(error)// error })
當上一個 then 中的回撥函式返回一個 Promise 物件時
timeout(1000).then((value) => { return timeout(1000); }).then((value) => { console.log(value);// resolve }, (error) => { console.log(error) })
一般來講,在 then 中的回撥函式返回一個 Promise 物件的場景都是 http 請求的巢狀,上述例子只是為了更方便的表示這種情況,實際場景中並不會去這麼使用。
上面說的是下一個 then 方法的行為跟前一個 then 方法的返回值有關,但如果一開始建立的 Promise 物件的狀態由 pending 變為 rejected 時,又是怎樣的情況呢?
function timeoutReject(ms) { return new Promise((resolve, reject) => { setTimeout(reject, ms, 'reject'); }); } timeoutReject(1000).then((value) => { console.log(value); }).then(undefined, (error) => { console.log(error)// error })
可以看到, Promise 物件的狀態變為失敗狀態時執行第二個 then 中的第二個回撥函式的程式碼。 實際上,Promise 物件的錯誤具有「冒泡」性質,會一直向後傳遞,直到被捕獲為止。
Promise.prototype.catch(onRejected)
Promise.prototype.catch(onRejected) 與 Promise.prototype.then(undefined, onRejected) 是一樣的效果,用於指定發生錯誤時的回撥函式。同樣的,catch 也返回一個新的 Promise 物件。
timeoutReject(1000).then((value) => { console.log(value); }).catch((error) => { console.log(error)// error })
而在上面說到,Promise 物件的錯誤具有「冒泡」性質,會一直向後傳遞,直到被捕獲為止。所以當前面的 Promise 物件丟擲錯誤時,則會依次冒泡,直到被 catch 捕獲。
timeoutReject(1000).then((value) => { console.log(value); }).then((value) => { console.log(value); }).catch((error) => { console.log(error)// error })
上述程式碼中,catch 會捕獲 timeoutReject() 和兩個 then 所返回的 Promise 物件所丟擲的錯誤。
一般來說,不要在 then 方法裡面定義 Reject 狀態的回撥函式(即 then 的第二個引數),總是使用 catch 方法。
Promise.all()
Promise.all 方法用於將多個 Promise 物件合成一個 Promise 物件,它接受一個數組作為引數,陣列中的每一個元素都是 Promise 物件。最終返回一個新的 Promise 物件,其狀態由傳入的 Promise 物件共同決定,分為以下兩種情況。
- 當傳入的 Promise 物件的狀態都變成 fulfilled 時,返回的新的 Promise 物件的狀態才會變成 fulfilled,其 then 方法的回撥函式的引數是一個數組,分別是傳入的 Promise 物件的返回值;
- 當傳入的 Promise 物件的狀態其中有一個變成 rejected 時,返回的新的 Promise 物件的狀態就會變成 rejected,其 then 方法的回撥函式的引數是第一個狀態變成 reject 的 Promise 物件的返回值。
傳入的 Promise 物件的狀態都變成 fulfilled 時
function timeout1(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'resolve'); }); } function timeout2(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'resolve'); }); } Promise.all([timeout1(1000), timeout2(2000)]).then((value) => { console.log(value); // [ 'resolve', 'resolve' ] }).catch((error) => { console.log(error) })
當傳入的 Promise 物件的狀態其中有一個變成 rejected 時
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'resolve'); }); } function timeoutReject(ms) { return new Promise((resolve, reject) => { setTimeout(reject, ms, 'reject'); }); } Promise.all([timeout(1000), timeoutReject(2000)]).then((value) => { console.log(value); }).catch((error) => { console.log(error)// reject })