本篇文章是包教包會,和你實現一個Promise的第三篇文章。由於這個小系列旨在完成一個符合規範的Promise,並且這三篇文章有先後關係,若是沒有看過前兩篇,第三篇看起來會有些莫名其妙。要是沒看過,仍是建議先看下前兩篇。在這裏:javascript
到目前爲止,咱們的MyPromise雖然能夠用了,可是還有一些地方不符合規範,再就是有些地方還有點問題,咱們來進行逐個修改。github
如今then方法的兩個參數是沒有默認值的,因此若是使用者在調用then方法時,沒有傳遞參數,後面咱們使用onResolved或者onRejected時,程序會報onResolved/onRejected is not a function
,這會致使咱們的Promise被迫終止,因此要給它們加上默認值。npm
MyPromise.prototype.then = function (onResolved, onRejected) {
// 看這裏,設置onResovled的默認值
if (typeof onResolved !== 'function') {
onResolved = function(value) {
return value
}
}
// 看這裏,設置onRejected的默認值
if (typeof onRejected !== 'function') {
onRejected = function(reason) {
throw reason
}
}
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
if (this.status === 'rejected') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
})
}
return promise2
}
複製代碼
上面經過tyepof判斷onResovled或者onRejected是否是function類型,若是不是,就給一個函數。這樣,不只避免了不傳值的狀況,也解決了使用隨便亂傳參數的問題。數組
對於onResolved的方法,咱們聲明一個函數直接返回value便可,對於onRejected的方法,咱們拋一個錯誤出來。這樣promise2也就能夠經過then方法拿到它們返回或者拋出的錯誤的。promise
咱們來看這裏的代碼,這裏只是其中一處:bash
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
// 看這裏看這裏
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
複製代碼
咱們知道,then方法執行時,用戶會寫兩個函數onResovled和onRejected而且傳進來,而後咱們調用它們來得到當前Promise實例resolve或者reject傳遞過來的值。可是問題來了,由於onResovled和onRejected是用戶實現、咱們調用的,那用戶寫onResolved或者onRejected時寫錯了,裏面語法有問題,執行的時候報錯了,那咱們的程序也會終止掉。因此咱們要針對這種可能性作一個try...catch捕捉。異步
總共有四個地方須要try...catch:函數
MyPromise.prototype.then = function (onResolved, onRejected) {
if (typeof onResolved !== 'function') {
onResolved = function(value) {
return value
}
}
if (typeof onRejected !== 'function') {
onRejected = function(reason) {
throw reason
}
}
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
// 這裏要try...catch
try {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
function failFn(reason) {
// 這裏要try...catch
try {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
// 這裏也要哦
try {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
// 這裏也要哦
try {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
return promise2
}
複製代碼
總共四個地方,若是onResolved或者onRejected執行報錯,會被catch到,而且直接用reject把promise2標記爲失敗狀態。then方法改完了,下面構造函數也要小小改一下。
構造函數有個地方要修改:resolve和reject裏的內容須要異步執行。 爲啥要這樣寫,其實我也不清楚,由於從邏輯上來看即便不寫彷佛也沒毛病。可是不它們設置成異步執行的話,有6個測試用例通不過。
因此我去看了它的測試用例,你也能夠看看,地址在這裏 :它是這麼測的:
specify("fulfilled after a delay", function (done) {
var d = deferred();
var isFulfilled = false;
d.promise.then(function onFulfilled() {
assert.strictEqual(isFulfilled, true);
done();
});
setTimeout(function () {
d.resolve(dummy);
isFulfilled = true;
}, 50);
});
複製代碼
根據規範,我只要保證onResolved在resolve以後執行就行,因此如今寫的代碼沒啥問題,但問題是它的測試用例,先聲明瞭一個isFulfilled,而後使用setTimeout調用resolve並將isFulfilled置爲true,並在onResolved判斷一個isFulfilled爲true。
可是,咱們是在resolve函數裏面就已經調用resolve了,因此此時isFufilled仍是false,因此咱們須要給resolve和reject裏的代碼都異步執行才能經過這裏的測試。
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
// 這裏加個setTimeout
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
})
}
let reject = (reason) => {
// 這裏也加個setTimeout
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
})
}
executor(resolve, reject)
}
複製代碼
測試很簡單,有專門的測試工具。
npm install -g promises-aplus-tests
複製代碼
同時還要將咱們的MyPromise暴露出去,並提供promise實例和resolve以及reject函數引用。
MyPromise.deferred = function() {
let dfd = {}
dfd.promise = new MyPromise(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
try {
module.exports = MyPromise
} catch (e) {}
複製代碼
咱們須要在MyPromise上寫一個靜態方法,執行後返回一個dfd對象。這個對象上有三個屬性:
promise
: 一個咱們要測試的Promise實例resolve
: 這個實例的resolve函數reject
: 這個實例的reject函數好了,到這裏看一下包括測試須要在內的全部的代碼:
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
})
}
let reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
})
}
executor(resolve, reject)
}
MyPromise.prototype.then = function (onResolved, onRejected) {
if (typeof onResolved !== 'function') {
onResolved = function(value) {
return value
}
}
if (typeof onRejected !== 'function') {
onRejected = function(reason) {
throw reason
}
}
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
try {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
function failFn(reason) {
try {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.status === 'rejected') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
return promise2
}
function resolve_promise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
if (x instanceof MyPromise) {
x.then(function(v) {
resolve_promise(promise2, v, resolve, reject)
}, function(t) {
reject(t)
})
return
}
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, function resolvePromise(y) {
if (called) return
called = true
resolve_promise(promise2, y, resolve, reject)
}, function rejectPromise(r) {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
MyPromise.deferred = function() {
let dfd = {}
dfd.promise = new MyPromise(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
try {
module.exports = MyPromise
} catch (e) {
}
複製代碼
開始測試:
promises-aplus-tests MyPromise.js
複製代碼
你將會看到:
872個測試用例所有經過!
接下來,咱們實現一些很是經常使用的方法,這些方法雖然不在規範裏,可是它們平時也有用武之地!
這個方法有如下特色:
MyPromise.all = function(arr) {
return new MyPromise(function(resolve, reject) {
let result = new Array(arr.length)
let count = 0
for(let i=0; i<arr.length; ++i) {
let currentPromise = arr[i]
currentPromise.then(function(res) {
result[i] = res
count++
if (count === arr.length) {
resolve(result)
}
}, function(reason) {
reject(reason)
})
}
})
}
複製代碼
很好理解,就很少講了~
注意,這個方法是實例方法:
MyPromise.prototype.catch = function(failFn) {
this.then(null, function(reason) {
failFn(reason)
})
}
複製代碼
很少說了
注意,這是一個靜態方法:
MyPromise.race = function(arr) {
return new MyPromise(function(resolve, reject) {
arr.forEach(promise => {
promise.then(resolve, reject)
})
})
}
複製代碼
完事~
直接返回一個成功狀態的Promise,靜態方法:
MyPromise.resolve = function(value) {
return new MyPromise(function(resolve, reject) {
resolve(value)
})
}
複製代碼
直接返回一個失敗狀態的Promise實例,靜態方法:
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason)
})
}
複製代碼
後面咱們實現的這些方法是沒有測試用例的哦~
好不容易完成了一個徹底符合規範的Promise,給它起一個帥氣的名字吧,就叫Zoro吧。Zoro是《海賊王》裏羅羅諾亞·索隆的英文名字,索隆在小時候曾經向古伊娜許諾要成爲世界最強劍豪 ,而如今索隆剛拿到閻魔這把刀,在成爲最強的道路上一路狂奔,因此我以爲這個名字再合適不過了!
完成的zoro地址在這裏 ,歡迎點贊,感謝您的閱讀!
(完)