當下js最難以處理的的就是異步的任務(wù)會什么時候完成和完成之后的回調(diào)問題虚婿。
而回調(diào)函數(shù)也是最基礎(chǔ)最常用的處理js異步操作的辦法,而無限的回調(diào)會讓代碼的可維護性變得非常差榄棵。難以掌控的觸發(fā)狀態(tài)躲胳,讓你自己寫的代碼當時還可以讀懂,但是過一段時間如果不重新理一下邏輯皮服,估計自己都會忘了。借用一個例子:
listen( "click", function handler(evt){
setTimeout( function request(){
ajax( "http://some.url.1", function response(text){
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
} );
}, 1000) ;
} );
doSomething();
這種地獄回調(diào)會讓代碼變得多么不可控参咙。
- 第一步:執(zhí)行l(wèi)istern()
- 第二步: doSomething()
- 第三步:1000ms后執(zhí)行ajax()
- 第四步:ajax完成后龄广,根據(jù)結(jié)果執(zhí)行handler或request
如果handler或request里面還有這樣的代碼,估計以后維護的同學(xué)來改個功能蕴侧,心里非常的不爽择同。
在你不知道的javascript一書中,對于回調(diào)的信任問題做了闡述 當你使用第三方的庫的方法處理回調(diào)時很有可能遇到以下信任內(nèi)容
基于這些問題净宵,Promise就橫空出世了敲才。
一、什么是Promise
Promise是異步編程的一種解決方案择葡,它有三種狀態(tài)紧武,分別是pending-進行中、resolved-已完成敏储、rejected-已失敗
當Promise的狀態(tài)又pending轉(zhuǎn)變?yōu)閞esolved或rejected時阻星,會執(zhí)行相應(yīng)的方法,并且狀態(tài)一旦改變已添,就無法再次改變狀態(tài)妥箕,這也是它名字promise-承諾的由來
二、Promise的基本用法
function loadImg(src) {
let promise = new Promise(function (resolve, reject) {
// 新建一個img標簽
let img = document.createElement('img')
// 圖片加載成功
img.onload = function () {
resolve(img)
}
// 圖片加載失敗
img.onerror = function () {
reject('圖片加載失敗')
}
img.src = src
})
return promise
}
let src ='http://img5.imgtn.bdimg.com/it/u=415293130,2419074865&fm=27&gp=0.jpg'
let result = loadImg(src)
result.then((img) => {
console.log(`加載成功更舞,img寬度=${img.width}`)
}, (text) => {
console.log(`error:${text}`)
})
- result把圖片地址傳入loadImg中畦幢,loadImg返回一個Promise對象。
- 根據(jù)圖片地址加載圖片缆蝉,加載成功就執(zhí)行resolve宇葱,反之失敗就執(zhí)行reject。
then方法接收兩個函數(shù)刊头,一個是成功(狀態(tài)為resolved)的回調(diào)函數(shù)贝搁,一個失敗(狀態(tài)為rejected)的回調(diào)函數(shù)芽偏。
then方法的返回值不是一個promise對象就會被包裝成一個promise對象,所以then方法支持鏈式調(diào)用弦讽。
再來個按順序執(zhí)行的函數(shù):
function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
這樣看著是不是非常順眼污尉。
三膀哲、Promise.prototype.then() VS Promise.prototype.catch()
.then()方法使Promise原型鏈上的方法,它包含兩個參數(shù)方法被碗,分別是已成功resolved的回調(diào)和已失敗rejected的回調(diào)
promise.then(
() => { console.log('this is success callback') },
() => { console.log('this is fail callback') }
)
.catch()的作用是捕獲Promise的錯誤某宪,與then()的rejected回調(diào)作用幾乎一致。但是由于Promise的拋錯具有冒泡性質(zhì)锐朴,能夠不斷傳遞兴喂,這樣就能夠在下一個catch()中統(tǒng)一處理這些錯誤。同時catch()也能夠捕獲then()中拋出的錯誤焚志,所以建議不要使用then()的rejected回調(diào)衣迷,而是統(tǒng)一使用catch()來處理錯誤
promise.then(
() => { console.log('this is success callback') }
).catch(
(err) => { console.log(err) }
)
同樣,catch()中也可以拋出錯誤酱酬,由于拋出的錯誤會在下一個catch中被捕獲處理壶谒,因此可以再添加catch()
使用rejects()方法改變狀態(tài)和拋出錯誤 throw new Error() 的作用是相同的
當狀態(tài)已經(jīng)改變?yōu)閞esolved后,即使拋出錯誤膳沽,也不會觸發(fā)then()的錯誤回調(diào)或者catch()方法
then() 和 catch() 都會返回一個新的Promise對象汗菜,可以鏈式調(diào)用
promise.then(
() => { console.log('this is success callback') }
).catch(
(err) => { console.log(err) }
).then(
...
).catch(
...
)
Promise實例的異步方法和then()中返回promise有什么區(qū)別?
// p1異步方法中返回p2
let p1 = new Promise ( (resolve, reject) => {
resolve(p2)
} )
let p2 = new Promise ( ... )
// then()中返回promise
let p3 = new Promise ( (resolve, reject) => {
resolve()
} )
let p4 = new Promise ( ... )
p3.then(
() => return p4
)
p1異步方法中返回p2
p1的狀態(tài)取決于p2挑社,如果p2為pending陨界,p1將等待p2狀態(tài)的改變,p2的狀態(tài)一旦改變痛阻,p1將會立即執(zhí)行自己對應(yīng)的回調(diào)菌瘪,即then()中的方法針對的依然是p1
then()中返回promise
由于then()本身就會返回一個新的promise,所以后一個then()針對的永遠是一個新的promise录平,但是像上面代碼中我們自己手動返回p4麻车,那么我們就可以在返回的promise中再次通過 resolve() 和 reject() 來改變狀態(tài)
Promise的其他api
Promise.resolve() / Promise.reject()
用來包裝一個現(xiàn)有對象,將其轉(zhuǎn)變?yōu)镻romise對象斗这,但Promise.resolve()會根據(jù)參數(shù)情況返回不同的Promise:
參數(shù)是Promise:原樣返回
參數(shù)帶有then方法:轉(zhuǎn)換為Promise后立即執(zhí)行then方法
參數(shù)不帶then方法动猬、不是對象或沒有參數(shù):返回resolved狀態(tài)的Promise
Promise.reject()會直接返回rejected狀態(tài)的Promise
Promise.all()
參數(shù)為Promise對象數(shù)組,如果有不是Promise的對象表箭,將會先通過上面的Promise.resolve()方法轉(zhuǎn)換
var promise = Promise.all( [p1, p2, p3] )
promise.then(
...
).catch(
...
)
當p1赁咙、p2、p3的狀態(tài)都變成resolved時免钻,promise才會變成resolved彼水,并調(diào)用then()的已完成回調(diào),但只要有一個變成rejected狀態(tài)极舔,promise就會立刻變成rejected狀態(tài)
Promise.race()
var promise = Promise.race( [p1, p2, p3] )
promise.then(
...
).catch(
...
)
“競速”方法凤覆,參數(shù)與Promise.all()相同,不同的是拆魏,參數(shù)中的p1盯桦、p2慈俯、p3只要有一個改變狀態(tài),promise就會立刻變成相同的狀態(tài)并執(zhí)行對于的回調(diào)
Promise.done() / Promise. finally()
Promise.done() 的用法類似 .then() ,可以提供resolved和rejected方法,也可以不提供任何參數(shù)拼弃,它的主要作用是在回調(diào)鏈的尾端捕捉前面沒有被 .catch() 捕捉到的錯誤
Promise. finally() 接受一個方法作為參數(shù),這個方法不管promise最終的狀態(tài)是怎樣刑峡,都一定會被執(zhí)行
四、大神實現(xiàn)的Promise源碼
下面是一個網(wǎng)上找的實現(xiàn)Promise的代碼玄柠。
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class PromiseDemo {
constructor(callback) {
this.status = PENDING;
this.value = null;
this.defferd = [];
setTimeout(
callback.bind(this, this.resolve.bind(this), this.reject.bind(this)
), 10);
};
resolve(result) {
this.status = FULFILLED;
this.value = result;
this.done();
}
reject(error) {
this.status = REJECTED;
this.value = error;
}
handle(fn) {
if (!fn) {
return;
}
var value = this.value;
var t = this.status;
var p;
if (t == PENDING) {
this.defferd.push(fn);
} else {
if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
p = fn.onfulfiled(value);
}
if (t == REJECTED && typeof fn.onrejected == 'function') {
p = fn.onrejected(value);
}
var promise = fn.promise;
if (promise) {
if (p && p.constructor == this) {
p.defferd = promise.defferd;
} else {
p = this;
p.defferd = promise.defferd;
this.done();
}
}
}
}
done() {
if (this.status == PENDING) {
return;
}
var defferd = this.defferd;
for (var i = 0; i < defferd.length; i++) {
this.handle(defferd[i]);
}
}
then(success, fail) {
var o = {
onfulfiled: success,
onrejected: fail
};
var status = this.status;
o.promise = new this.constructor(function () { });
if (status == PENDING) {
this.defferd.push(o);
} else if (status == FULFILLED || status == REJECTED) {
this.handle(o);
}
return o.promise;
}
}