1.promise的含義
Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
物件。
所謂Promise
,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。
Promise
物件有以下兩個特點。
(1)物件的狀態不受外界影響。Promise
物件代表一個非同步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise
物件的狀態改變,只有兩種可能:從pending
變為fulfilled
和從pending
變為rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise
物件新增回撥函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
有了Promise
物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise
物件提供統一的介面,使得控制非同步操作更加容易。
Promise
也有一些缺點。首先,無法取消Promise
,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise
內部丟擲的錯誤,不會反應到外部。第三,當處於pending
狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
2.小試promise用法:
Promise
建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve
和reject
。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。
resolve
函式的作用是,將Promise
物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject
函式的作用是,將Promise
物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。
var p=new Promise(function(resolve,rejcet){
setTimeout(function(){
if(true){
//非同步操作成功
resolve('success');
}else{
//非同步操作失敗
rejcet('failure');
}
},1000);
});
p.then(function(value){
//成功的回撥
console.log(value);
},function(error){
//失敗的回撥
console.log(error);
});複製程式碼
從demo中可以看到,在ES6中Promise
物件是一個建構函式,用來生成Promise
例項。並且Promise
例項生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回撥函式。
3.手寫一個promise,徹底掌握它的原理
首先,我們先搭建好程式碼的骨架:
function Promise(callback) {
var self = this //promise例項
self.status = 'PENDING' // Promise當前的狀態
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve時的回撥函式集
self.onRejectedCallback = [] // Promise reject時的回撥函式集
callback(resolve, reject) // 執行executor並傳入相應的引數
function resolve(value){
}
function reject(error){
}
}
// 新增我們的then方法
Promise.prototype.then=function(){
}複製程式碼
我們先建立一個Promise建構函式,並傳入一個回撥函式callback,callback裡面傳入兩個函式作為引數,一個是resove,一個是reject。並在Promise的原型上加入我們的then方法。框架搭好了,接下來我們來一點點的完善框架裡面的內容,可以這麼說,把resolve,reject和then補充完,基本可以說就是把Promise完成了。
然後緊接著,我們完善一下resolve和reject:
function Promise(callback) {
var self = this
self.status = 'PENDING' // Promise當前的狀態
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve時的回撥函式集
self.onRejectedCallback = [] // Promise reject時的回撥函式集
callback(resolve, reject) // 執行executor並傳入相應的引數
function resolve(value){
if(self.status=='PENDING'){
self.status=='FULFILLED';
self.data=value;
// 依次執行成功之後的函式棧
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
}
function rejecte(error){
if (self.status === 'PENDING') {
self.status = 'REJECTED'
self.data = error;
// 依次執行失敗之後的函式棧
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](error)
}
}
}
}複製程式碼
接下來我們實現我們的then方法:
then方法是Promise的核心,因此這裡會花比較大的篇幅去介紹then:
一個promise的then接受兩個引數:
promise.then(onFulfilled, onRejected)複製程式碼
onFulfilled
和 onRejected
都是可選引數。
- 如果
onFulfilled
不是函式,其必須被忽略 - 如果
onRejected
不是函式,其必須被忽略
onFulfilled 特性
如果 onFulfilled
是函式:
- 當
promise
執行結束後其必須被呼叫,其第一個引數為promise
的終值,也就是resolve傳過來的值 - 在
promise
執行結束前其不可被呼叫 - 其呼叫次數不可超過一次
onRejected 特性
如果 onRejected
是函式:
- 當
promise
被拒絕執行後其必須被呼叫,其第一個引數為promise
的據因,也就是reject傳過來的值 - 在
promise
被拒絕執行前其不可被呼叫 - 其呼叫次數不可超過一次
呼叫時機
onFulfilled
和 onRejected
只有在執行環境堆疊僅包含平臺程式碼時才可被呼叫(平臺程式碼指引擎、環境以及 promise 的實施程式碼)
呼叫要求
onFulfilled
和 onRejected
必須被作為函式呼叫(即沒有 this
值,在嚴格模式(strict) 中,函式 this
的值為 undefined
;在非嚴格模式中其為全域性物件。)
多次呼叫
then
方法可以被同一個 promise
呼叫多次
- 當
promise
成功執行時,所有onFulfilled
需按照其註冊順序依次回撥 - 當
promise
被拒絕執行時,所有的onRejected
需按照其註冊順序依次回撥
返回
then
方法必須返回一個 promise
物件
promise2 = promise1.then(onFulfilled, onRejected);複製程式碼
- 如果
onFulfilled
或者onRejected
返回一個值x
,則執行下面的 Promise 解決過程:[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
丟擲一個異常e
,則promise2
必須拒絕執行,並返回拒因e
- 如果
onFulfilled
不是函式且promise1
成功執行,promise2
必須成功執行並返回相同的值 - 如果
onRejected
不是函式且promise1
拒絕執行,promise2
必須拒絕執行並返回相同的據因
不論 promise1
被 reject 還是被 resolve 時 promise2
都會被 resolve,只有出現異常時才會被 rejected。
每個Promise物件都可以在其上多次呼叫then方法,而每次呼叫then返回的Promise的狀態取決於那一次呼叫then時傳入引數的返回值,所以then不能返回this,因為then每次返回的Promise的結果都有可能不同。
接下來我們來寫我們的then方法:
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
// 根據標準,如果then的引數不是function,則我們需要忽略它,此處以如下方式處理
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
if (self.status === 'resolved') {
// 如果promise1(此處即為this/self)的狀態已經確定並且是resolved,我們呼叫onResolved
// 因為考慮到有可能throw,所以我們將其包在try/catch塊裡
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果
x.then(resolve, reject)
}
resolve(x) // 否則,以它的返回值做為promise2的結果
} catch (e) {
reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
}
})
}
// 此處與前一個if塊的邏輯幾乎相同,區別在於所呼叫的是onRejected函式,就不再做過多解釋
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {
// 如果當前的Promise還處於pending狀態,我們並不能確定呼叫onResolved還是onRejected,
// 只能等到Promise的狀態確定後,才能確實如何處理。
// 所以我們需要把我們的**兩種情況**的處理邏輯做為callback放入promise1(此處即this/self)的回撥陣列裡
// 邏輯本身跟第一個if塊內的幾乎一致,此處不做過多解釋
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
// 為了下文方便,我們順便實現一個catch方法
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}複製程式碼
最後附上完整的程式碼:
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
// 根據標準,如果then的引數不是function,則我們需要忽略它,此處以如下方式處理
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
if (self.status === 'resolved') {
// 如果promise1(此處即為this/self)的狀態已經確定並且是resolved,我們呼叫onResolved
// 因為考慮到有可能throw,所以我們將其包在try/catch塊裡
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果
x.then(resolve, reject)
}
resolve(x) // 否則,以它的返回值做為promise2的結果
} catch (e) {
reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
}
})
}
// 此處與前一個if塊的邏輯幾乎相同,區別在於所呼叫的是onRejected函式,就不再做過多解釋
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {
// 如果當前的Promise還處於pending狀態,我們並不能確定呼叫onResolved還是onRejected,
// 只能等到Promise的狀態確定後,才能確實如何處理。
// 所以我們需要把我們的**兩種情況**的處理邏輯做為callback放入promise1(此處即this/self)的回撥陣列裡
// 邏輯本身跟第一個if塊內的幾乎一致,此處不做過多解釋
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
// 為了下文方便,我們順便實現一個catch方法
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}複製程式碼