2015年6月,ES2015(即ES6)正式釋出後受到了非常多的關注。其中很重要的一點是 Promise 被列為了正式規範。
在此之前很多庫都對非同步程式設計/回撥地獄實現了類 Promise 的應對方案,比如 bluebird、Angular 的 Q 和大名鼎鼎的 jQuery 的 deffered 等。
為了便於理解,本文將分為三個部分,每個部分實現 Promise 的一部分特性,最終一步一步的實現一個完整的、遵循 promise A+ 規範的 Promise。
Promise A+ 規範規定,Promise 有三種狀態,分別是 pending(預設狀態,代表等待)、fulfilled(代表成功)、rejected(代表失敗)。
來看看 Promise 的用法,以及它有哪些特性。
var fn = new Promise(function (resolve, reject) {
// 非同步操作
setTimeout(function() {
resolve('resolve 01')
// 由於reslove和 reject 是互斥的,因為已經呼叫了 resolve,這裡reject不會執行
reject('reject 01')
}, 500)
})
fn().then(function (data) {
console.log('成功1:', data)
return new Promise(function (resolve, reject) {
reject('reject 02')
}
)}, function (err) {
console.log('失敗1:', err)
})
.then(function (data) {
console.log('成功2:', data)
}, function (err) {
console.log('失敗2:', err)
}).then(function (data) {
console.log('成功2:', data)}, function (err) {
console.log('失敗2:', err)
})複製程式碼
結果:
可以看到,Promise 通常會有這些特性:
①、Promise 類有 then 方法,then 方法有兩個引數,分別是 Promise 成功和失敗的回撥,且二者互斥,呼叫了其中一個,另外一個就不會執行
②、Promise 支援鏈式呼叫,then 的返回值可以是一個 Promise,也可以是一個普通值,如果是 一個普通的值,那麼就會當做下一個 then 成功的回撥函式的引數
③、Promis 還有其它擴充套件方法
說了一堆有的沒的,下面就開始來實現這個東西吧~
=================================================
一,Promise 類的實現
=> Promise 有 then 方法:
var Promise = function (executor) {
console.log('證明不是用的原生 Promise')
var _this = this
this.status = 'pending'
// 預設狀態,可以轉化為 resolved 和 rejected
this.successVal = undefined
this.failVal = undefined
// 執行了成功或失敗後,要將狀態對應修改成成功和失敗
function resolve (val) {
if ( _this.status === 'pending' ) {
_this.status = 'resolved'
_this.successVal = val
}
}
function reject (val) {
if ( _this.status === 'pending' ) {
_this.status = 'rejected'
_this.failVal = val
}
}
try {
// 應該還記得,Promise 的引數是一個函式吧,我們稱之為 executor(執行器)
// 同時這個函式接收2個引數,分別是成功和失敗的回撥函式
executor(resolve, reject)
} catch (e) {
// 如果發生異常,直接reject捕獲
reject(e)
}
}
// then 方法接收2個引數,分別是成功和失敗的回撥函式
Promise.prototype.then = function (onFulfilled, onRejected) {
var _this = this
// 顯然要根據當前狀態來執行成功或失敗的回撥了
if ( _this.status === 'resolved' ) {
onFulfilled(_this.successVal)
}
if ( _this.status === 'rejected' ) {
onFulfilled(_this.failVal)
}
}複製程式碼
來試試效果:
var fn = new Promise(function (resolve, reject) {
resolve('oooook~')
})
fn.then(function (data) {
console.log('success: ', data)}, function (err) {
console.log('err: ', err)
})複製程式碼
結果:
結果看上去很美。但如果改成下面這樣呢?
var fn = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('oooook~')
}, 500)
})
fn.then(function (data) {
console.log('success: ', data)
}, function (err) {
console.log('err: ', err)
})複製程式碼
結果:ok,問題來了,Promise 費大力氣搞出來可不是隻能做同步呼叫的,畢竟有了 ajax 之後前端才會發展到今天這麼繁榮,所以需要讓我們的 Promise 支援非同步。
修改如下(註釋內是對新增程式碼的說明):
var Promise = function (executor) {
console.log('證明不是用的原生 Promise 的一句廢話')
var _this = this this.status = 'pending'
// 預設狀態,可以轉化為 resolved 和 rejected
this.successVal = undefined
this.failVal = undefined
// ----------------- 表示新增程式碼 -------------------- // 用於存放成功和失敗的回撥
this.onFulfilledList = []
this.onRejectedList = []
function resolve (val) {
if ( _this.status === 'pending' ) {
_this.status = 'resolved'
_this.successVal = val
// -------------- 執行所有的成功回撥 ---------------
_this.onFulfilledList.forEach(function(fn){
fn()
})
}
}
function reject (val) {
if ( _this.status === 'pending' ) {
_this.status = 'rejected'
_this.failVal = val
// -------------- 執行所有的失敗回撥 ---------------
_this.onRejectedList.forEach(function(fn){
fn()
})
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
var _this = this
if ( _this.status === 'resolved' ) {
onFulfilled(_this.successVal)
}
if ( _this.status === 'rejected' ) {
onFulfilled(_this.failVal)
}
// ----------------- 對非同步呼叫的處理 -------------------
// 說明:如果是非同步呼叫,走到 then 方法裡的時候,status 還沒有被修改,仍然
// 是 pending 狀態,所以這時候需要再回去執行 executor 裡的對應方法,而對應的
// 方法會將對應的存放回撥的 list 裡的方法執行(類似釋出-訂閱模式一樣的處理)
if ( _this.status === 'pending' ) {
_this.onFulfilledList.push(function () {
onFulfilled(_this.successVal)
})
_this.onRejectedList.push(function () {
onRejected(_this.failVal)
})
}
}複製程式碼
看看效果: