- 最早接觸
Promise
概念是在創(chuàng)業(yè)公司嫩絮,同事引入的Promise
第三方庫,用在用Swift
寫的工程中围肥。 - 異步編程剿干,用
block
已經(jīng)是主流,分為成功穆刻、失敗置尔、最終三個(gè)分支。如果嵌套氢伟,那么將一層一層地嵌套進(jìn)去榜轿。在真正執(zhí)行的時(shí)候,“從內(nèi)向外”一層一層執(zhí)行朵锣∶危“從外到內(nèi)”寫,但是“從內(nèi)到外”執(zhí)行诚些,并且還是一層層嵌套飞傀,這個(gè)不符合人的思維習(xí)慣。Promise
就是將這種情況統(tǒng)一調(diào)整為“從左到右”诬烹,書寫和執(zhí)行都一樣砸烦。并且把一層層的嵌套修改為一連串的點(diǎn)點(diǎn)點(diǎn)函數(shù)調(diào)用,這個(gè)“很人性化”绞吁。 - 在函數(shù)式編程中幢痘,
curry
和compose
操作都是“從右向左”的,而Monad
就是將操作改為“很人性化”的“從左向右”家破,書寫和執(zhí)行都是颜说。并且還可以把嵌套flatMap
一下购岗,“鋪平”,從而形成一連串點(diǎn)點(diǎn)點(diǎn)函數(shù)調(diào)用门粪。 -
Promise
和Monad
針對的領(lǐng)域不同藕畔,不過都達(dá)到了相同的目標(biāo)(點(diǎn)點(diǎn)點(diǎn)函數(shù)鏈?zhǔn)秸{(diào)用,從左向右書寫和執(zhí)行)庄拇∽⒎可以把Promise
看成是一種專門用于異步領(lǐng)域的Monad
什么是Promise
-
Promise
是抽象異步處理對象以及對其進(jìn)行各種操作的組件。 - 回調(diào)函數(shù)是處理異步過程的常用方式措近,傳給回調(diào)函數(shù)的參數(shù)為(
error
對象溶弟, 執(zhí)行結(jié)果)組合。 -
Node.js
等規(guī)定在JavaScript
的回調(diào)函數(shù)的第一個(gè)參數(shù)為Error
對象瞭郑,這也是它的一個(gè)慣例辜御。 - 而
Promise
則是把類似的異步處理對象和處理規(guī)則進(jìn)行規(guī)范化, 并按照采用統(tǒng)一的接口來編寫屈张,而采取規(guī)定方法之外的寫法都會(huì)出錯(cuò)擒权。
基本概念
- 要想創(chuàng)建一個(gè)
promise
對象、可以使用new
來調(diào)用Promise
的構(gòu)造器來進(jìn)行實(shí)例化阁谆。
var promise = new Promise(function(resolve, reject) {
// 異步處理
// 處理結(jié)束后碳抄、調(diào)用resolve 或 reject
});
對通過
new
生成的promise
對象為了設(shè)置其值在resolve
(成功) /reject
(失敗)時(shí)調(diào)用的回調(diào)函數(shù) 可以使用promise.then()
實(shí)例方法。onFulfilled、onRejected
兩個(gè)都為可選參數(shù)。一般情況围详,onFulfilled
會(huì)用,onRejected
省略璧尸,一般用catch
比較多
promise.then(onFulfilled, onRejected);
promise.then
成功和失敗時(shí)都可以使用。異常的時(shí)候調(diào)用promise.catch(onRejected)
像
Promise
這樣的全局對象還擁有一些靜態(tài)方法熬拒。包括Promise.all()
還有Promise.resolve()
等在內(nèi)爷光,主要都是一些對Promise
進(jìn)行操作的輔助方法。用
new Promise
實(shí)例化的promise
對象有以下三個(gè)狀態(tài)澎粟。其中 左側(cè)為在ES6 Promises
規(guī)范中定義的術(shù)語蛀序, 而右側(cè)則是在Promises/A+
中描述狀態(tài)的術(shù)語“埔椋基本上狀態(tài)在代碼中是不會(huì)涉及到的哼拔,所以名稱也無需太在意引有。
(1)"has-resolution" - Fulfilled: resolve(成功)
時(shí)瓣颅。此時(shí)會(huì)調(diào)用onFulfilled
(2)"has-rejection" - Rejected: reject
(失敗)時(shí)。此時(shí)會(huì)調(diào)用onRejected
(3)"unresolved" - Pending:
既不是resolve
也不是reject
的狀態(tài)譬正。也就是promise
對象剛被創(chuàng)建后的初始化狀態(tài)等
-
promise
對象的狀態(tài)宫补,從Pending
轉(zhuǎn)換為Fulfilled
或Rejected
之后檬姥, 這個(gè)promise
對象的狀態(tài)就不會(huì)再發(fā)生任何變化。也就是說粉怕,Promise
與Event
等不同健民,在.then
后執(zhí)行的函數(shù)可以肯定地說只會(huì)被調(diào)用一次。
基本流程
-
new Promise(fn)
返回一個(gè)promise
對象 - 在
fn
中指定異步等處理
處理結(jié)果正常的話贫贝,調(diào)用resolve(處理結(jié)果值);
處理結(jié)果錯(cuò)誤的話秉犹,調(diào)用reject(Error對象);
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 運(yùn)行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
- 本質(zhì)上還是回調(diào)函數(shù)。將回調(diào)函數(shù)用
promise
對象包裝一下稚晚。 -
resolve
方法的參數(shù)并沒有特別的規(guī)則崇堵,基本上把要傳給回調(diào)函數(shù)參數(shù)放進(jìn)去就可以了。 (then
方法可以接收到這個(gè)參數(shù)值) - 傳給
reject
的參數(shù)也沒有什么特殊的限制客燕,一般只要是Erro
r對象(或者繼承自Error
對象)就可以鸳劳。 - 其實(shí)
.catch
只是promise.then(undefined, onRejected)
的別名而已。一般說來也搓,使用.catch
來將resolve
和reject
處理分開來寫是比較推薦的做法赏廓。 -
Promise.resolve
作為new Promise()
的快捷方式,在進(jìn)行promise
對象的初始化或者編寫測試代碼的時(shí)候都非常方便傍妒。
異步執(zhí)行順序
在使用Promise.resolve(value)
等方法的時(shí)候幔摸,如果promise
對象立刻就能進(jìn)入resolve
狀態(tài)的話,那么你是不是覺得.then
里面指定的方法就是同步調(diào)用的呢颤练?
實(shí)際上抚太,.then
中指定的方法調(diào)用是異步進(jìn)行的。下面是一個(gè)例子:
var promise = new Promise(function (resolve){
console.log("inner promise"); // 1
resolve(42);
});
promise.then(function(value){
console.log(value); // 3
});
console.log("outer promise"); // 2
- 由于
JavaScript
代碼會(huì)按照文件的從上到下的順序執(zhí)行昔案,所以最開始<1>
會(huì)執(zhí)行尿贫,然后是resolve(42);
被執(zhí)行。這時(shí)候promise
對象的已經(jīng)變?yōu)榇_定狀態(tài)踏揣,FulFilled
被設(shè)置為了42
庆亡。 - 下面的代碼
promise.then
注冊了<3>
這個(gè)回調(diào)函數(shù),但是這個(gè)是異步的捞稿,這里不會(huì)馬上執(zhí)行又谋。 - 函數(shù)
<2>
會(huì)最先被調(diào)用,最后才會(huì)調(diào)用回調(diào)函數(shù)<3> 娱局。
輸出結(jié)果:
inner promise // 1
outer promise // 2
42 // 3
明明可以以同步方式進(jìn)行調(diào)用的函數(shù)彰亥,非要使用異步的調(diào)用方式,這是在Promise
設(shè)計(jì)上的規(guī)定方針衰齐。
不要對異步回調(diào)函數(shù)進(jìn)行同步調(diào)用 Promise
是符合這一點(diǎn)的任斋。
方法鏈的形式
在Promise
里可以將任意個(gè)方法連在一起作為一個(gè)方法鏈(method chain)
。
aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
console.log(error);
});
如果把在then
中注冊的每個(gè)回調(diào)函數(shù)稱為task
的話耻涛,那么我們就可以通過Promise
方法鏈方式來編寫能以taskA → task B
這種流程進(jìn)行處理的邏輯了废酷。
- 每次調(diào)用
then
都會(huì)返回一個(gè)新創(chuàng)建的promise
對象瘟檩。如果不用鏈?zhǔn)秸{(diào)用,那么就沒有順序執(zhí)行的效果澈蟆,要極力避免墨辛。
// 1: 對同一個(gè)promise對象同時(shí)調(diào)用 `then` 方法
var aPromise = new Promise(function (resolve) {
resolve(100);
});
aPromise.then(function (value) {
return value * 2;
});
aPromise.then(function (value) {
return value * 2;
});
aPromise.then(function (value) {
console.log("1: " + value); // => 100
})
// 這種是過程式調(diào)用,沒有順序執(zhí)行的功能趴俘,每個(gè)函數(shù)的參數(shù)value都是100睹簇,需要極力避免這種調(diào)用
// 2: 對 `then` 進(jìn)行 promise chain 方式進(jìn)行調(diào)用
var bPromise = new Promise(function (resolve) {
resolve(100);
});
bPromise.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("2: " + value); // => 100 * 2 * 2 = 400
});
// 這種鏈?zhǔn)秸{(diào)用,才是期望的樣子寥闪。
// 就算是簡單的值(這里是100)带膀,在傳遞的時(shí)候,傳的也是promise對象橙垢,中間有“裝箱”垛叨,“拆箱”的操作。
// then函數(shù)柜某,就是那種輸入輸出都是“類型”的函數(shù)嗽元,能夠形成鏈?zhǔn)秸{(diào)用。所以說promise是monad的一種實(shí)現(xiàn)
- 在實(shí)際使用中喂击,要避免下面的錯(cuò)誤用法
function badAsyncCall() {
var promise = Promise.resolve();
promise.then(function() {
// 任意處理
return newVar;
});
return promise;
}
在 promise.then
中產(chǎn)生的異常不會(huì)被外部捕獲剂癌,也不能得到then
的返回值,即使其有返回值翰绊。原因是promise !== promise.then()
- 應(yīng)該該為下面的正確形式
function anAsyncCall() {
var promise = Promise.resolve();
return promise.then(function() {
// 任意處理
return newVar;
});
}
多個(gè)異步調(diào)用進(jìn)行統(tǒng)一處理
- 為了應(yīng)對這種需要對多個(gè)異步調(diào)用進(jìn)行統(tǒng)一處理的場景佩谷,
Promise
準(zhǔn)備了Promise.all
和Promise.race
這兩個(gè)靜態(tài)方法。 -
Promise.all
接收一個(gè)promise
對象的數(shù)組作為參數(shù)监嗜,當(dāng)這個(gè)數(shù)組里的所有promise
對象全部變?yōu)?code>resolve或reject
狀態(tài)的時(shí)候谐檀,它才會(huì)去調(diào)用.then
方法。 - 傳遞給
Promise.all
的promise
并不是一個(gè)個(gè)的順序執(zhí)行的裁奇,而是同時(shí)開始桐猬、并行執(zhí)行的。
// `delay`毫秒后執(zhí)行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
// 所有promise變?yōu)閞esolve后程序退出
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
// 約128ms
console.log(values); // [1,32,64,128]
});
// 輸出:有調(diào)度損耗刽肠,線程間同步損耗溃肪,理論上應(yīng)該輸出128ms,實(shí)際輸出145ms是合理的音五。多執(zhí)行幾次惫撰,這個(gè)值每次都會(huì)變
// 145ms
// [ 1, 32, 64, 128 ]
- 4個(gè)
promise
是并行執(zhí)行的- 4個(gè)
promise
都執(zhí)行完畢后再執(zhí)行then
- 傳遞的
values
數(shù)組和promise
數(shù)組的順序一致
-
Promise.race
只要有一個(gè)promise
對象進(jìn)入FulFilled
或者Rejected
狀態(tài)的話,就會(huì)繼續(xù)進(jìn)行后面的處理躺涝。
// `delay`毫秒后執(zhí)行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
// 任何一個(gè)promise變?yōu)閞esolve或reject 的話程序就停止運(yùn)行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});
-
Promise.race
在第一個(gè)promise
對象變?yōu)?code>Fulfilled之后厨钻,并不會(huì)取消其他promise
對象的執(zhí)行。
var winnerPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('winner funtion running');
resolve('this is winner');
}, 10);
});
var loserPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('loser funtion running');
resolve('this is loser');
}, 1000);
});
// 第一個(gè)promise變?yōu)閞esolve后程序停止
Promise.race([winnerPromise, loserPromise]).then(function (value) {
console.log(value); // => 'this is winner'
});
// 輸出:
// winner funtion running
// this is winner
// loser funtion running
- 數(shù)組中
[winnerPromise, loserPromise]
中的promise
同時(shí)開始執(zhí)行10ms
后winnerPromise
勝出,輸出winner funtion running
Promise.race()
函數(shù)有了結(jié)果莉撇,就是winnerPromise
呢蛤,順序執(zhí)行then
惶傻,輸出傳遞的value
棍郎,這里是this is winner
。這個(gè)就是winnerPromise
的resolve
信息resolve('this is winner');
loserPromise
對象沒有被取消银室,繼續(xù)執(zhí)行涂佃。1000ms
后,loserPromise
對象中的函數(shù)執(zhí)行蜈敢,輸出loser funtion running
*loserPromise
對象中的resolve
信息'resolve('this is loser');
不會(huì)被傳遞辜荠。
錯(cuò)誤處理
-
.then
和.catch
都會(huì)創(chuàng)建并返回一個(gè) 新的promise
對象。Promise
實(shí)際上每次在方法鏈中增加一次處理的時(shí)候所操作的都不是完全相同的promise
對象抓狭。
使用
promise.then(onFulfilled, onRejected)
的話伯病,在onFulfilled
中發(fā)生異常的話,在onRejected
中是捕獲不到這個(gè)異常的否过。原因是他們在promise chain
中處于同一級在
promise.then(onFulfilled).catch(onRejected)
的情況下.then
中產(chǎn)生的異常能在.catch
中捕獲午笛。因?yàn)?code>.catch處于.then
中的下一級.then
和.catch
在本質(zhì)上是沒有區(qū)別的,所不同的是在整個(gè)promise chain
中所處的級別不同苗桂。.catch
更靠后一點(diǎn)药磺,更可靠一點(diǎn)。如果一定要用
.then
來進(jìn)行錯(cuò)誤處理煤伟,那么就要新增一個(gè)下一級的.then
來處理
function throwError(value) {
// 拋出異常
throw new Error(value);
}
// <1> onRejected不會(huì)被調(diào)用
function badMain(onRejected) {
return Promise.resolve(42).then(throwError, onRejected);
}
// <2> 有異常發(fā)生時(shí)onRejected會(huì)被調(diào)用
function goodMain(onRejected) {
return Promise.resolve(42).then(throwError).catch(onRejected);
}
// <3> 使用下一級的then
function nextThenMain(onRejected) {
return Promise.resolve(42).then(throwError).then(null, onRejected);
}
// 運(yùn)行示例
badMain(function(){
console.log("BAD");
});
goodMain(function(){
console.log("GOOD");
});
nextThenMain(function(){
console.log("Next Then");
});
// 輸出:
// GOOD
// Next Then
這里的badMain
癌佩,之所以捕獲不到錯(cuò)誤,是因?yàn)橛昧送患壍?code>.then
錯(cuò)誤處理統(tǒng)一使用.catch
便锨,不要用.then
- 主動(dòng)拋異常围辙,使用
reject
而不是throw
// 不要用throw,這雖然能運(yùn)行放案,但很不promise風(fēng)格酌畜,要避免
var promise = new Promise(function(resolve, reject){
throw new Error("message");
});
promise.catch(function(error){
console.error(error);// => "message"
});
// 這是推薦的方式,形成習(xí)慣
var promise = new Promise(function(resolve, reject){
reject(new Error("message"));
});
promise.catch(function(error){
console.error(error);// => "message"
})
- 在.then中使用reject卿叽,推薦用下面的格式
var onRejected = console.error.bind(console);
var promise = Promise.resolve();
promise.then(function () {
return Promise.reject(new Error("this promise is rejected"));
}).catch(onRejected);
// this promise is rejected