鏈式呼叫
上文有提到標準 Promise 的一些特性,接下來一起來實現 Promise 的鏈式呼叫。
來看看標準 Promise 的鏈式呼叫:
var fn = new Promise(function (resolve, reject) {
setTimeout(function() {
resolve('oook~')
}, 500)
})
fn.then(function(data) {
console.log('data-1: ', data)
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject('不oook~')
}, 1000)
})
}, function(err) {
console.log('err-1: ', err)
})
.then(function(data) {
console.log('data-2:', data)
}, function(err) {
console.log('err-2: ', err)
})
.then(function(data) {
console.log('data-3:', data)
}, function(err) {
console.log('err-3: ', err)
})
複製程式碼
可以看到,經過0.5秒後輸出 data-1: oook~,又經過1秒後輸出 err-2: 不oook~,最後輸出 data-3: undefined
特徵如下:
- then 方法可以返回普通值,也可以返回一個 Promise 物件。
- 如果 then 方法返回的是一個 Promise 物件,則會根據這個 Promise 物件裡執行的是 resolve 還是 reject 走後一個 then 方法的成功和失敗回撥(本例在第一個 then 裡返回了一個 Promise 物件,並非同步呼叫了 reject ,所以會輸出 err-2: 不oook~)。
- 如果 then 方法返回的是一個普通值(無論走的成功還是失敗),那麼這個普通值就會被當做下一個 then 裡成功回撥函式的引數(上面例子最後打出 err-3: undefined 就是因為函式預設返回了普通值 undefined)。
根據以上特徵,我們的 Promise 物件需要增加以下處理:
- 只有 Promise 物件才有 then 方法,所以我們的 then 方法要支援鏈式呼叫的話,需要返回一個新的 Promise 物件,並在這個物件裡對要傳遞的值進行處理
- 由於 then 方法的返回值可能是一個普通值,也可能是一個新的 Promise 物件,Promise 物件裡可能又是一個 Promise 物件,所以我們需要對這個返回值做一個統一的處理,本例用一個統一的方法 resolvePromise 來處理
程式碼如下:
Promise.prototype.then = function (onFulfilled, onRejected) {
var _this = this
// 定義一個promise2變數(別問我為什麼這麼命名,A+規範裡就是這麼命名的)
var promise2
if (_this.status === 'fulfilled') {
// 返回一個新的promise
promise2 = new Promise(function (resolve, reject) {
// A+ 規範規定,onFulfilled 和 onRejected 都需要非同步執行,所以加上一個定時器
setTimeout(function() {
try {
// x可能是個普通值,也可能是個promise
var x = onFulfilled(_this.successVal)
// x也可能是第三方的 Promise 庫,用一個函式統一來處理
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
});
}
if (_this.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function() {
try {
var x = onRejected(_this.failVal)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
// 當呼叫then時,如果是非同步操作,就會處於pending狀態
if (_this.status === 'pending') {
// 將成功的回撥新增到陣列中
promise2 = new Promise(function (resolve, reject) {
_this.onFulfilledList.push(function () {
// 可以看到,我們在每一個 resolve 和 reject 執行的地方都加上了非同步
setTimeout(function() {
try {
var x = onFulfilled(_this.successVal)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
_this.onRejectedList.push(function () {
setTimeout(function() {
try {
var x = onRejected(_this.failVal)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
})
}
return promise2
}
function resolvePromise(p2, x, resolve, reject) {
// 不能返回自己
if (p2 === x) {
return reject(new TypeError('不能迴圈引用!'))
}
// 上一篇有說過,resolve 和 reject 不能同時執行,所以用一個鎖來當開關
var called
// x返回的可能是物件和函式也可能是一個普通的值
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
var then = x.then
if (typeof then === 'function') {
then.call(x, function (y) {
// ------- resolve 和 reject 不能同時執行 -------
if (called) {
return
}
called = true
// 如果成功的話,y有可能也是個 promise,所以遞迴繼續解析
resolvePromise(p2, y, resolve, reject)
}, function (e) {
// ------- resolve 和 reject 不能同時執行 -------
if (called) {
return
}
called = true
reject(e)
})
} else {
// 如果是普通值,直接返回成功
resolve(x)
}
} catch(e) {
if (called) {
return
}
called = true
reject(e)
}
} else {
resolve(x)
}
}
複製程式碼
ok,鏈式呼叫的處理基本上就完成了,下面來看看效果:
var p = new Promise(function(resolve, reject){
setTimeout(function() {
resolve('完全ooooook~')
}, 500)
})
p.then(function (data) {
console.log('success1: ', data)
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject('啊啊啊啊啊啊啊啊啊')
}, 1000)
})
}, function (err) {
console.log('failed: ', err)
})
.then(function (data) {
console.log('success2: ', data)
}, function(err) {
console.log('failed2: ', err)
})
複製程式碼
結果如下:
結果完全ooooook啊啊啊啊啊啊