一文讀懂Promise
什麼是Promise
$.ajax({
success: (res) => {
$.ajax({
success: (res) => {
$.ajax({
success: (res) => {
//...
}
})
}
})
}
})
複製程式碼
這就是典型的回撥地獄,不僅程式碼臃腫,可讀性差,而且耦合度過高,不易維護。程式碼無法複用,還容易隱藏bug。
Promise規範的出現就是為了解決這種問題。
這裡強調一下,Promise是一種解決方案,它是一種規範。
ES6原生提供了Promise物件,在日常開發中經常會接觸到Promise相關操作,本文將介紹Promise的使用方法及相關原理,希望能有所幫助。
Promise有三種狀態:
- Pending(等待態)
- Fulfilled(成功態)
- Rejected(失敗態)
Promise的特點是狀態只能由Pending轉換為Fulfilled或Rejected,並且一旦改變不再更改。
new Promise((resolve,reject) => {
/*executor*/
})
複製程式碼
Promise是一個帶有resolve
,reject
executor
)。這個建構函式在Promise建立的時候立即執行,resolve和reject兩個函式在被呼叫時,分別將Promise的狀態更改為fulfilled(成功態)和rejected(失敗態),並傳遞成功的值或失敗的原因。executor
內部通常會執行一些非同步操作,當非同步操作執行完畢時,可根據執行結果相應地呼叫resolve或reject(可能成功可能失敗)。如果executor丟擲一個錯誤,那麼該Promise也將轉為失敗態。
Promise原型上還具有then和catch方法,呼叫後返回一個Promise例項,因此可以被鏈式呼叫。
每一個Promise例項都帶有一個then方法,該方法有兩個引數(onFulfilled
onRejected
),分別為Promise成功和失敗時的回撥。
let p = new Promise((resolve,reject) => {
resolve('success');
})
p.then(value => {
console.log(value); //success
}).then(value => {
console.log(value); //沒有返回值時,undefined
})
複製程式碼
當返回了一個值或者一個成功態的Promise時,then返回的Promise都是成功態,若沒有返回值時也是成功態,而回調函式的引數值為undefind
。
當丟擲一個錯誤或者返回一個失敗態Promise,then返回的Promise都是失敗態。
let p = new Promise((resolve,reject) => {
reject('fail');
})
p.then(() => {
},err => {
console.log(err); //fail
});
複製程式碼
then方法傳遞的成功/失敗函式,這兩個函式可以返回一個promise;如果返回的是一個promise,則該promise狀態將作為下一次then的結果。
let p = new Promise((resolve,reject) => {
resolve('success')
});
let p2 = new Promise((resolve,reject) => {
resolve('success2')
})
p.then(value => {
return p2; //p2的成功態將作為下一次then的狀態
}).then(value => {
console.log(value)
})
複製程式碼
Promise.prototype.catch
方法返回也是一個Promise,使用方式同then方法,用於處理異常捕獲情況。該方法捕獲屬於就近捕獲原則,離錯誤最近的一個catch將會捕獲錯誤並進行處理,後續將不再捕獲。
若失敗狀態被then方法中失敗函式回撥觸發,則catch方法將不進行捕獲。
let p1 = new Promise((resolve,reject) => {
resolve('success');
});
p1.then(value => {
throw 'a new error';
}).catch(e => {
console.log(e); //a new error
}).then(() => {
console.log('after a catch the chain is restored');
})
複製程式碼
Promise.all
方法返回一個新的Promise物件,在引數物件中所有的promise物件都成功時才會觸發成功態,一旦有任意一個物件失敗則立即觸發該Promise物件的失敗態。當觸發成功態後,會把一個包含所有Promise返回值的陣列按順序返回。如果觸犯失敗態,則會把第一個失敗的Promise物件錯誤資訊返回。
let p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('p1')
},0)
});
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('p2')
},0)
});
let promise = Promise.all([p1,p2]).then(value => {
console.log(value); //['p1','p2']
})
複製程式碼
Promise.race方法是把引數中任意第一個完成狀態轉換的Promise作為成功或失敗狀態,並返回該Promise物件。
let p1 = new Promise((resolve,1000)
});
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('p2')
},0)
});
Promise.race([p1,p2]).then((value) => {
console.log(value)
},(err) => {
console.log(err); //p2
})
複製程式碼
Promise.resolve(value)返回一個成功態,並且將value傳遞給對應的then方法;
Promise.reject(reason)返回一個失敗態,並且將reason傳遞個對應的then方法。
Promise的優缺點
優點 | 缺點 |
---|---|
解決回撥 | 無法監測進行狀態 |
鏈式呼叫 | 新建立即執行且無法取消 |
減少巢狀 | 內部錯誤無法丟擲 |
Promise練習(Node環境)
1.等待狀態
const promise1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
const promise2 = promise1.then(() => {
return 'an error'
})
console.log('promise1',promise1)
console.log('promise2',promise2)
setTimeout(() => {
console.log('promise1',promise1)
console.log('promise2',promise2)
},2000)
複製程式碼
2.狀態改變
const promise = new Promise((resolve,reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise.then((res) => {
console.log('then: ',res)
})
.catch((err) => {
console.log('catch: ',err)
})
複製程式碼
3.鏈式呼叫
Promise.resolve(1)
.then((res) => {
console.log(res)
return 2
})
.catch((err) => {
return 3
})
.then((res) => {
console.log(res)
})
複製程式碼
4.鏈式呼叫
const promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log('executor')
resolve('success')
},1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res,Date.now() - start)
})
promise.then((res) => {
console.log(res,Date.now() - start)
})
//Promise的建構函式只執行一次,並且立即執行;promise的then方法可以被多次呼叫,每次都返回新的promise例項。
複製程式碼
const promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log('executor')
resolve('success')
},1000)
})
const promise2 = promise.then(res => {
console.log(res);
})
promise2.then(res => {
console.log(res)
})
複製程式碼
5.錯誤捕獲
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ',res)
})
.catch((err) => {
console.log('catch: ',err)
})
複製程式碼
Promise.resolve()
.then(() => {
throw new Error('error'); //return Promise.reject('error')
})
.then((res) => {
console.log('then: ',err)
})
複製程式碼
then或catch方法返回一個錯誤物件並不會被捕獲,只有在丟擲錯誤或返回失敗態時才會引起捕獲。
6.引用錯誤
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(e => {
console.log(e)
})
//[TypeError: Chaining cycle detected for promise #<Promise>]
複製程式碼
返回值不能是promise例項本身,因為此時例項並未構建完成,造成死迴圈,因此丟擲型別錯誤。
7.事件環
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
複製程式碼
8.穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
複製程式碼
then和catch方法的引數是一個函式,若非函式則發生穿透
Promise A+規範
該網站介紹了若需要實現Promise需要實現的規範方法。當多個庫實現自己的Promise類時,為了能實現相應的效果,該規範提供了實現標準,並提供了promises-aplus-tests
測試包測試實現能力。
後續將推出文章介紹如何實現自己的Promise類,敬請期待!