Promise 是 JavaScript 非同步編程中的重要概念,是目前較為流行的 JavaScript 非同步編程解決方案之一。它最早由社區提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 對象。 Promise 的基本概念 一個 有以下幾種狀態: pending: 初始狀態,既不是成功 ...
Promise
Promise
是 JavaScript 非同步編程中的重要概念,是目前較為流行的 JavaScript 非同步編程解決方案之一。它最早由社區提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
對象。
Promise 的基本概念
一個 Promise
有以下幾種狀態:
- pending: 初始狀態,既不是成功,也不是失敗狀態(也可以說是進行中)。
- fulfilled: 意味著操作成功完成。
- rejected: 意味著操作失敗。
pending 狀態的 Promise 對象可能觸發 fulfilled 狀態並傳遞一個值給相應的狀態處理方法,也可能觸發失敗狀態(rejected)並傳遞失敗信息。當其中任一種情況出現時,Promise 對象的
then
方法綁定的處理方法(handlers )就會被調用(then方法包含兩個參數:onfulfilled 和 onrejected,它們都是 Function 類型。當Promise狀態為 fulfilled 時,調用 then 的 onfulfilled 方法,當 Promise 狀態為 rejected 時,調用 then 的 onrejected 方法, 所以在非同步操作的完成和綁定處理方法之間不存在競爭)。 ------MDN
因為Promise.prototype.then
和 Promise.prototype.catch
方法會返回 promise 對象,所以它們可以被鏈式調用。
Promise 的基本用法
// 這是 Promise 的常用方法
function promise(...) {
return new Promise((resolve, reject) => {
// do something
if (/* fulfilled (非同步操作成功) */) {
return resolve(value);
}
reject(error);
});
}
promise(...).then((value) => {
// success
}, (error) => {
// failure
});
Promise
構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供。
當狀態從 pending
變為fulfilled
時,即非同步操作成功,這時調用resolve
,並將非同步操作的結果,作為參數傳遞出去。當狀態從 pending
變為rejected
時,即非同步操作失敗,這時調用reject
,並將error
作為參數傳遞出去。
Promise
實例生成後,通過then
方法部署fulfilled
狀態和rejected
狀態的回調函數。
下麵是一個用Promise
封裝 Ajax 的例子:
const getJSON = function({
// 解構賦值設置預設參數
url = 'url',
method = 'GET',
async = false,
data = null
} = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(url, method, async);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
let status = this.status;
if (status >= 200 && status < 300 || status == 304) {
resolve(this.response);
} else {
reject(this.statusText);
}
}
}
xhr.send(data);
});
}
getJSON({url: '/json'})
.then(json => console.log(json), error => console.log(error));
Promise 的鏈式調用
以上面封裝的 Ajax 為例子:
getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.catch( error => {...} )
.then( () => {...} );
因為then
或者catch
方法會返回一個Promise
對象,或者自己return
一個Promise
對象,而這樣就可以繼續使用then
或者catch
方法。
Promise.prototype.catch()
這裡的catch()
與try/cath
的方法類似,都是捕捉錯誤進行處理。
// 這兩種寫法是等價的
// 第一種
getJSON({url: '/json'}).then( (json) => {
// success
}).catch( (error) => {
// failure
});
// 第二種
getJSON({url: '/json'}).then((json) => {
// success
},(error) => {
// failure
});
但比較建議使用第一種寫法,因為使用catch
讓代碼更接近於同步代碼,而且在鏈式調用中,使用catch
方法可以捕捉前面所有的錯誤進行一併處理。
Promise
對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止,而且catch
還會返回一個Promise
對象,這樣可以繼續使用then
方法進行鏈式調用。
getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.then( json => {...} )
.catch( error => {
// 這裡可以處理前面所有 Promise 的錯誤
}).then( () => {...} );
Promise.prototype.finally()
finally
裡面的操作不管 Promise 對象最後狀態如何,都是會去執行的,這和 JAVA 的 finally 有點相似,即無論如何,一定執行。
promise(...)
.then(result => {···})
.catch(error => {···})
.finally(() => {
// 這裡的操作一定執行
});
值得註意的是,finally
方法的回調函數是不接受任何參數的,這意味著沒有辦法知道前面的 Promise 狀態到底是fulfilled
還是rejected
。所以finally
方法裡面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。
Promise.resolve()
Promise.resolve(value)
方法返回一個以給定值解析後的promise
對象。但如果這個值是個 thenable(即帶有then方法),返回的promise會“跟隨”這個thenable的對象,採用它的最終狀態(指resolved/rejected/pending/settled);
如果傳入的value本身就是promise對象,則該對象作為Promise.resolve方法的返回值返回;
否則以該值為成功狀態返回promise對象。
傳入參數是一個
promise
對象如果傳入參數本身就是 Promise 實例,那麼
Promise.resolve
將不做任何修改、原封不動地返回這個實例。const p0 = new Promise((resolve, reject) => { resolve(1); }); const p1 = Promise.resolve(p0); console.log(p1 === p0); // true
傳入參數是
thenable
對象Promise.resolve()
會將這個對象轉為 Promise 對象,然後立即執行thenable
對象的then
方法,執行完畢之後,Promise 對象狀態變為fulfilled
狀態,然後就會執行後續的then
方法。// thenable 即帶有 then 方法的對象 let thenable = { then: (resolve, reject) => { resolve(1); } }; const p2 = Promise.resolve(thenable); p2.then( value => console.log(value) );
傳入參數為其他值
如果參數不為前面的兩種的情況,則
Promise.resolve
方法會以fulfilled
狀態返回一個新的 Promise 對象,並將傳入的參數傳給then
方法。const p3 = Promise.resolve(123); p3.then( value => console.log(value) ); // 123
Promise.reject()
Promise.reject(value)
會以rejected
狀態返回一個 Promose 實例,並將傳入參數原封不動地傳給then
或者catch
方法。
const p4 = Promise.reject('錯誤');
// then()
p4.then(null, (err)=>{
console.log(err); // 錯誤
});
// catch()
p4.catch( err => console.log(err) ); // 錯誤
Promise.all()
Promise.all(iterable)
方法返回一個Promise
實例,此實例在iterable
參數內所有的promise
狀態都為fulfilled
狀態(即成功)或參數中不包含promise
時回調完成;如果參數中promise
有一個狀態為rejected
狀態(即失敗)時回調失敗,失敗的原因是第一個失敗promise
的結果。
一般來說,Promise.all
方法會傳入一個數組參數,如果數組成員里有非Promise
實例時,會自動調用上面所說的Promise.resolve
方法將其轉為Promise
實例,再進行處理。
然後處理結果分兩種情況:
1.參數內所有Promise
實例的狀態都為fulfilled
狀態,則返回一個包含所有resolve
結果的數組傳給後續的回調函數。
2.參數內有一個Promise
實例的狀態為rejected
狀態,則將第一個被reject
的Promise
實例的結果傳給後續的回調函數。
const p5 = Promise.resolve(123);
const p6 = 42;
const p7 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p5, p6, p7]).then((values) => {
console.log(values); // [123, 42, "foo"]
});
Promise.race()
Promise.race
方法與上面的Promise.all()
類似,但不同的是,Promise.race()
是只要傳入的Promise
實例中有一個狀態改變了,就會直接返回這個最快改變狀態的Promise
實例的結果,而不是返回所有。
const p8 = Promise.resolve(123);
const p9 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.race([p8, p9]).then((values) => {
// 這裡只會獲得 p8 的返回值
console.log(values); // 123
});
手動實現 Promise.all()
有的面試會讓實現Promise.all()
方法,這個還算簡單的,有的還要手撕。Promise
首先來理清一下實現思路:
遍歷執行傳入的
Promise
實例。- 如果傳入的參數成員有不是
Promise
實例的,會直接調用Promise.resolve()
進行處理。 - 返回一個新的
Promise
實例,分兩種情況返回結果。 fulfilled
狀態返回的數組結果要按順序排列。
實現代碼如下:
function promiseAll(promises) {
// 返回一個新的 Promise 實例
return new Promise((resolve, reject) => {
let count = 0;
let promisesNum = promises.length;
let resolvedValue = new Array(promisesNum);
for (let i=0; i<promisesNum; i++) {
// 用 Promise.resolve() 處理傳入參數
Promise.resolve(promises[i])
.then((value) => {
// 順序排列返回結果
resolvedValue[i] = value;
if (++count == promisesNum) {
return resolve(resolvedValue);
}
})
.catch((err) => {
// 返回第一個 rejected 狀態的結果
return reject(err);
});
}
});
}
測試如下:
// 成功的情況
const p1=Promise.resolve(1);
const p2=Promise.resolve(2);
const p3=Promise.resolve(3);
promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
// 失敗的情況
const p1=Promise.resolve(1);
const p2=Promise.reject('失敗');
const p3=Promise.resolve(3);
promiseAll([p1, p2, p3])
.then(console.log)
.catch(console.log); // 失敗
備註
春招開始了,有點不敢投,躊躇著,也焦慮著。