目錄
- 一. Promise 簡(jiǎn)介
- Promise 是什么埂奈?
- 我們?yōu)槭裁葱枰?Promise仰猖?
- Promise 能解決什么扯罐?
- Promise 的特點(diǎn)
- Promise 的缺點(diǎn)
- 二. Promise API 解析
- Promise 的構(gòu)造方法
- Promise 的實(shí)例方法
- Promise 類(lèi)級(jí)別方法
- 三. Promise 深度剖析
- Promise 與 setTimeout
- 多層級(jí) .then()
一. Promise 簡(jiǎn)介
1. Promise 是什么橄登?
ES6 出現(xiàn)的目標(biāo)是為了使 JavaScript 語(yǔ)言可以編寫(xiě)大型的復(fù)雜應(yīng)用程序轧膘,使之成為企業(yè)級(jí)的開(kāi)發(fā)語(yǔ)言熬荆,Promise 也是其中的一環(huán)舟山。
從語(yǔ)法上講 Promise 是個(gè)內(nèi)置對(duì)象,抽象來(lái)看卤恳,它像是一個(gè)容器累盗,里面保存著一個(gè)異步操作的執(zhí)行結(jié)果,Promise 提供了一套 API 以保證所有異步操作的統(tǒng)一處理方法突琳。
2. 我們?yōu)槭裁葱枰?Promise若债?
我們先來(lái)舉一個(gè)簡(jiǎn)單的 “栗子”,某人需要做三件事(A拆融,B蠢琳,C)啊终,并且要按照這個(gè)順序依次執(zhí)行,現(xiàn)在將這個(gè)轉(zhuǎn)換為傳統(tǒng)的代碼方式:
// 首先我們要先定義這三件事(A傲须,B蓝牲,C)分別為三個(gè)函數(shù)
// 這三個(gè)函數(shù)都需要提供一個(gè)回調(diào)來(lái)表示事情的結(jié)束
function A(callback) {
console.log('Do A.');
callback();
}
function B(callback) {
console.log('Do B.');
callback();
}
function C(callback) {
console.log('Do C.');
callback();
}
// 現(xiàn)在,如果我想要依次做這三件事我需要這樣泰讽。例衍。。
A(function() {
// 其它的一些代碼
B(function() {
// 其它的一些代碼
C(function() {
// 其它的一些代碼
});
});
});
可以看出已卸,當(dāng)涉及到異步操作時(shí)佛玄,曾經(jīng)的大部分方式都是靠回調(diào)的嵌套來(lái)實(shí)現(xiàn)的,然而這樣的方式造成了幾個(gè)十分嚴(yán)重的問(wèn)題:
- 出現(xiàn)了多層回調(diào)累澡,當(dāng)加上業(yè)務(wù)代碼后翎嫡,將會(huì)使整體顯得臃腫且凌亂。
- 回調(diào)的出現(xiàn)導(dǎo)致邏輯的流程不清晰永乌,不具有可讀性和維護(hù)性惑申。
- 當(dāng)嵌套層級(jí)過(guò)多時(shí)會(huì)產(chǎn)生大量無(wú)用數(shù)據(jù)的滯留以及數(shù)據(jù)的混雜。
- 使用回調(diào)函數(shù)便完全浪費(fèi)了 return 和 throw 關(guān)鍵字的能力翅雏。
綜上所述圈驼,我們需要一種更好的解決辦法在某些(并不是全部)方面來(lái)取代回調(diào)(callback)方式的異步操作。
3. Promise 能解決什么望几?
- 編寫(xiě)大型應(yīng)用時(shí)绩脆,一種高級(jí)、實(shí)用且能解決實(shí)際問(wèn)題的高大上語(yǔ)法橄抹。
- 避免層層嵌套的回調(diào)函數(shù)靴迫,將異步操作以同步操作的流程表達(dá)出來(lái),使邏輯更加清晰楼誓。
- Promise 提供統(tǒng)一的接口來(lái)進(jìn)行異步操作玉锌。
4. Promise 的特點(diǎn)
Promise 是基于狀態(tài)的,一個(gè) Promise 對(duì)象表示了一個(gè)異步操作疟羹,而這個(gè)操作會(huì)有三種狀態(tài):Pending(進(jìn)行中)主守、Resolved(已成功)和 Rejected(已失敗)榄融。只有異步操作的執(zhí)行結(jié)果可以決定是哪一種狀態(tài)参淫,任何其它操作都無(wú)法改變。而且愧杯,一旦狀態(tài)改變涎才,就不會(huì)再變化。
Promise 的異步操作不需要像回調(diào)一樣執(zhí)行異步操作后立即就會(huì)調(diào)用回調(diào)力九,Promise 允許任何時(shí)候都可以得到這個(gè)異步操作的結(jié)果耍铜。也就是說(shuō)它其實(shí)和事件的機(jī)制是完全不同的邑闺,事件觸發(fā)時(shí),如果你沒(méi)有處于監(jiān)聽(tīng)狀態(tài)业扒,那么錯(cuò)過(guò)了就再也得不到此次事件的結(jié)果检吆,而 Promise 則是將結(jié)果狀態(tài)會(huì)凝固舒萎,等待你去觀察這個(gè)異步操作的結(jié)果程储。
5. Promise 的缺點(diǎn)
一旦構(gòu)造了 Promise 實(shí)例就代表執(zhí)行了一個(gè)異步操作,也就是說(shuō)它會(huì)立即執(zhí)行臂寝,并且中途無(wú)法取消章鲤。
如果不設(shè)置回調(diào),Promise 內(nèi)部拋出的錯(cuò)誤咆贬,不會(huì)反應(yīng)到外部败徊。(有利有弊,具體看怎么用)
Promise 其實(shí)只適合單一的異步程序掏缎,并不適合不斷發(fā)生的事件處理皱蹦,所以使用時(shí),要找好最適合使用的場(chǎng)景眷蜈。
二. Promise API 解析
1. Promise 構(gòu)造方法
Promise 本身就是個(gè)構(gòu)造函數(shù)沪哺,用來(lái)生成 Promise 實(shí)例。
Promise 構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù)酌儒,而該函數(shù)的兩個(gè)參數(shù)分別為 resolve 和 reject辜妓,它們分別是兩個(gè)函數(shù),由 JavaScript 引擎提供忌怎。
resolve 函數(shù)的作用是將 Promise 對(duì)象的狀態(tài)從 Pending 變?yōu)?Resolved籍滴,在異步操作成功時(shí)調(diào)用,并將異步操作的結(jié)果作為參數(shù)傳遞出去榴啸。
reject 函數(shù)的作用是將 Promise 對(duì)象的狀態(tài)從 Pending 變?yōu)?Rejected孽惰,在異步操作失敗時(shí)調(diào)用,并將異步操作失敗所拋出的錯(cuò)誤鸥印,作為參數(shù)傳遞出去灰瞻。
const promise = new Promise((resolve, reject) => {
// 模擬一個(gè)異步操作
setTimeout(function() {
if ( /* 異步操作成功 */ ) {
// 異步操作成功,攜帶載荷將狀態(tài)變?yōu)?resolved
resolve(payload);
} else {
// 異步操作失敗辅甥,攜帶錯(cuò)誤將狀態(tài)變?yōu)?rejected
reject(error);
}
}, 1000);
});
Promise 實(shí)例生成后酝润,異步操作就已經(jīng)開(kāi)始執(zhí)行了。
要注意璃弄,resolve 和 reject 的調(diào)用是對(duì) Promise 對(duì)象狀態(tài)的變更和數(shù)據(jù)的傳遞要销,并不會(huì)影響函數(shù)的執(zhí)行,所以 resolve 和 reject 后面如果有可以正常執(zhí)行的流程代碼夏块,它們?nèi)匀粫?huì)被正常執(zhí)行疏咐。如果不想這樣纤掸,可以使用 return 強(qiáng)制函數(shù)執(zhí)行的結(jié)束。
2. Promise 實(shí)例方法
(1) Promise.prototype.then()
Promise 實(shí)例生成以后浑塞,可以用 then 方法來(lái)指定 Resolved 狀態(tài)和 Rejected 狀態(tài)的回調(diào)函數(shù)
promise.then((payload) => {
// Resolved 時(shí)執(zhí)行
}, (error) => {
// Rejected 時(shí)執(zhí)行
});
Promise.prototype.then() 的兩個(gè)參數(shù)都需要傳遞函數(shù)借跪,代表著兩個(gè)狀態(tài)轉(zhuǎn)變所要執(zhí)行的回調(diào),每個(gè)回調(diào)都可以接受 Promise 對(duì)象狀態(tài)轉(zhuǎn)變時(shí)傳出的值作為參數(shù)酌壕。其中掏愁,then() 的第二個(gè)函數(shù)是可選的。
回到一開(kāi)始我們舉的 “栗子”卵牍,現(xiàn)在果港,我想通過(guò) Promise 的方式來(lái)實(shí)現(xiàn)多個(gè)異步程序的依次執(zhí)行,我們可以在 then() 的調(diào)用中顯式的返回一個(gè)新的 Promise 實(shí)例糊昙,然后就可以鏈?zhǔn)降那乙来蔚膱?zhí)行 then()辛掠,看下面的代碼:
const promise = new Promise((resolve, reject) => {
// 模擬異步程序 1:睡覺(jué) 1s
setTimeout(() => {
resolve('睡完覺(jué)了');
}, 1000);
});
promise.then((val) => {
console.log(val);
return (new Promise((resolve, reject) => {
// 模擬異步程序 2:吃飯 1s
setTimeout(() => {
resolve('吃完飯了');
}, 1000);
}));
}).then((val) => {
console.log(val);
return (new Promise((resolve, reject) => {
// 模擬異步程序 3:喝水 1s
setTimeout(() => {
resolve('喝完水了');
}, 1000);
}));
}).then((val) => {
console.log(val);
});
通過(guò)在 then() 的第一個(gè)回調(diào)函數(shù)中,返回新的 Promise 實(shí)例释牺,我們可以用同步的流程將異步的操作表示出萝衩,相比使用 callback 更加的邏輯清晰。
(2) Promise.prototype.catch()
Promise.prototype.catch 方法是 .then(null, rejection) 的別名没咙,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)猩谊,也可以捕獲 then() 運(yùn)行中所拋出的錯(cuò)誤。
一般來(lái)說(shuō)镜撩,好的方式是不再 then() 里面指定 Rejected 的回調(diào)预柒,而是使用 catch() 來(lái)對(duì)所有錯(cuò)誤的捕獲(包括 then() 里面拋出的錯(cuò)誤)
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
promise.then(() => {
return temp + 2;
}).catch((error) => {
console.error(error);
});
// ReferenceError: temp is not defined
3. Promise 類(lèi)級(jí)別方法
(1)Promise.all()
Promise.all() 方法用于將多個(gè) Promise 實(shí)例包裝成一個(gè)新的 Promise 實(shí)例。
Promise.all() 接受一個(gè)類(lèi)數(shù)組(具有 Iterator)作為參數(shù)袁梗,每個(gè)成員應(yīng)為 Promise 實(shí)例宜鸯,如果不是,會(huì)調(diào)用 Promise.resolve() 方法將其轉(zhuǎn)換為 Promise 實(shí)例遮怜。
Promise.all() 返回的新的 Promise 實(shí)例的狀態(tài)由傳遞的類(lèi)數(shù)組成員的共同狀態(tài)決定:
- 當(dāng)所有成員的狀態(tài)都變?yōu)?Resolved淋袖,Promise 實(shí)例的狀態(tài)才會(huì)變?yōu)?Resolved,此時(shí)所有成員的返回值組成一個(gè)數(shù)組锯梁,傳遞給 Promise 實(shí)例的回調(diào)函數(shù)
- 只要有一個(gè)成員的狀態(tài)變?yōu)?Rejected即碗,Promise 實(shí)例的狀態(tài)就變成 Rejected,此時(shí)第一個(gè)被 Rejected 的實(shí)例的返回值會(huì)傳遞給 Promise 實(shí)例的回調(diào)函數(shù)
如果作為 Promise.all() 參數(shù)的 Promise 實(shí)例自己定義了 catch 方法陌凳,那么它的狀態(tài)變?yōu)?Rejected 時(shí)剥懒,只會(huì)觸發(fā)它自己的 catch(),不會(huì)觸發(fā) Promise.all() 的 catch()
(2)Promise.race()
Promise.race() 方法和 Promise.all() 幾乎是一樣的合敦,只是對(duì)狀態(tài)的處理存在差別初橘。
Promise.race() 返回的新的 Promise 實(shí)例的狀態(tài)由傳遞的類(lèi)數(shù)組成員中最先改變狀態(tài)的成員的狀態(tài)決定。
(3)Promise.resolve()
Promise.resolve() 將現(xiàn)有對(duì)象轉(zhuǎn)換為 Promise 對(duì)象。
Promise.resolve() 的參數(shù)分成四種情況:
- Promise 實(shí)例:直接返回這個(gè)實(shí)例保檐。
- thenable 對(duì)象
- thenable 對(duì)象指的是具有 then 方法的對(duì)象耕蝉。
- Promise.resolve() 會(huì)將這個(gè)對(duì)象轉(zhuǎn)為 Promise 對(duì)象,然后就立即執(zhí)行 thenable 對(duì)象的 then 方法夜只,相當(dāng)于將這個(gè)對(duì)象的 then 方法作為 Promise 構(gòu)造函數(shù)的參數(shù)垒在,返回一個(gè)新的 Promise 實(shí)例。
- 其余情況
- Promise.resolve() 返回一個(gè)新的 Promise 對(duì)象扔亥,狀態(tài)為 Resolved场躯,使用 then() 時(shí),會(huì)將 Promise.resolve() 的參數(shù)傳遞給 then() 的回調(diào)函數(shù)中砸王。
- 這個(gè)立即 Resolved 的 Promise 對(duì)象推盛,實(shí)在本次 “事件循環(huán)” 的結(jié)束時(shí)才開(kāi)始執(zhí)行峦阁。
(4)Promise.reject()
Promise.reject() 會(huì)返回一個(gè)新的 Promise 實(shí)例谦铃,狀態(tài)為 Rejected。
Promise.reject() 等價(jià)于下面的寫(xiě)法:
Promise.reject(obj);
// 等價(jià)于
new Promise((resolve, reject) => reject(obj));
Promise.reject() 會(huì)將參數(shù)原封不動(dòng)的作為 reject() 的參數(shù)榔昔。
三. Promise 深度剖析
1. Promise 與 setTimeout
我們來(lái)看這樣的一段代碼:
setTimeout(() => {
console.log(1);
}, 0);
(new Promise((resolve, reject) => {
resolve();
})).then(() => {
console.log(2);
});
console.log(3);
這段代碼的運(yùn)行結(jié)果的順序是 3驹闰,2,1撒会,原因如下:
- 對(duì)于 setTimeout 我們應(yīng)該都知道嘹朗,它是放在下一輪 “事件循環(huán)” 的開(kāi)始,所以它一定要在本輪事件結(jié)束后才會(huì)執(zhí)行诵肛,也就是輸出 1 一定要在輸出 3 以后
- 接下來(lái)就是 Promise 異步執(zhí)行的問(wèn)題屹培,雖然 Promise 實(shí)例中并沒(méi)有真正意義上的異步程序,而是直接將狀態(tài)變更為 Resolved怔檩,且立即使用 then 進(jìn)行狀態(tài)的觀察褪秀,但是實(shí)質(zhì)上,立即 Resolved 的 Promise 是在本輪事件循環(huán)的末尾執(zhí)行薛训,總是晚于本輪循環(huán)的同步任務(wù)媒吗,所以,輸出 2 一定要在輸出 3 以后
- 最后乙埃,就是 Promise 和 setTimeout 之間的問(wèn)題闸英,上面也說(shuō)到了,setTimeout 是在下一輪事件的開(kāi)始介袜,而 Promise 又實(shí)在本一輪事件的結(jié)束甫何,所以,很明顯遇伞,輸出 1 要在輸出 2 以后
2. 多層級(jí) .then()
一個(gè) Promise 實(shí)例可以連續(xù)使用 .then() 來(lái)綁定回調(diào)函數(shù):
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
promise.then(() => {
console.log(1);
}).then(() => {
console.log(2);
});
// 1
// 2
如果在 .then() 的回調(diào)中顯式的指定一個(gè)返回值(非 Promise 實(shí)例)辙喂,這個(gè)值會(huì)被作為下一個(gè)鏈?zhǔn)?.then() 回調(diào)函數(shù)中的參數(shù):
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
promise.then(() => {
console.log(1);
return 'Promise';
}).then((val) => {
console.log(val);
});
// 1
// Promise
如果在 .then() 的回調(diào)中返回的是一個(gè) Promise 實(shí)例,那么下一個(gè)鏈?zhǔn)?.then() 會(huì)在這個(gè) Promise 實(shí)例的狀態(tài)變更時(shí)會(huì)被調(diào)用,并且 resolve 所傳遞的值會(huì)被作為下一個(gè)鏈?zhǔn)?.then() 回調(diào)函數(shù)中的參數(shù):
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
});
promise.then(() => {
console.log(1);
return (new Promise((resolve, reject) => {
setTimeout(function() {
resolve('Promise');
}, 1000);
}));
}).then((val) => {
console.log(val);
});
// 1
// Promise