实现then的链式调用
我们接着上一篇讲
上一篇最终代码如下:
详细代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
constructor(executor) {
executor(this.resolve, this.reject)
}
resolve = value => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value
while (this.successCallback.length) this.successCallback.shift()(value)
}
reject = reason => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()(reason)
}
then(successCallback, failCallback){
if (this.status === FULFILLED) {
successCallback(this.value)
}
else if (this.status === REJECTED) failCallback(this.reason)
else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
}
接下来的目标是实现 then的链式调用,首先看一个示例
let promise = new Promise((resolve, reject) => {
resolve('成功')
})
promise.then(value => {
console.log(value) // 成功
return '谁偷了我的奶酪'
})
.then(res => {
console.log(res) // 谁偷了我的奶酪
return 4
})
第一个then 返回了一个 字符串, 然后接着可以 往后 .then 获取上一个then返回的值 上一个then的值是哪里来的呢,我们看源码中 是不是 successCallback(this.value) 这个返回的值, 那么 then 返回的 肯定是一个新的 promise, 不然也无法 再次 .then,所以我们 大概有了一个思路
...
class MyPromise {
...
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
let res = successCallback(this.value)
resolve(res)
}
...
})
return promise2
}
}
在代码中 我 new 了 MyPromise 并将之返回, 这样 then 就返回了一个 promise, 那么 then 的返回值如何拿到呢? 通过 resolve(successCallback(this.value)),下一个then就可以拿到值了. 这样我们将上面的示例中 的 Promise 换成 MyPromise 也可以成功了. 不过,这是最简单的情况, then 的返回值有很多情况,我们需要一一来处理.
- then 返回普通值
- then 返回一个 promise
- then 返回了自身的promise
- then 里面传的不是一个函数
then 返回普通值 或 promise
then返回普通值按照上面的处理方法就可以完成,不过为了处理后面几种问题,我们需要创建一个方法专门处理这几种情况
...
class MyPromise {
...
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject)
}
...
})
return promise2
}
}
function resolvePromise(res, resolve, reject) {
// then 返回的是 promise
if (res instanceof MyPromise) {
res.then(value => resolve(value), reason => reject(reason))
}
else {
// then返回的是普通值
resolve(res)
}
}
// 测试
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
promise.then(value => {
console.log(value) // 成功
// 返回一个 promise
return new MyPromise((resolve, reject) => {resolve('newPromise')})
})
.then(res => {
console.log(res) // newPromise
return 4
})
在上图中,我们定一个了 resolvePromise 返回, 接受了上面的三个参数, 如果是普通值, 直接 调用 resolve 将 then 返回的普通值传递给下一个promise的then, 如果 then 返回的是 promise,通过调用 这个 promise的then 得到它返回的值, 不管是成功还是失败,我们都要一直往下传,所以目前我们已经解决两种 promise 返回值的情况
then 返回了自身的promise
首先我们看用原生promise调用的一个示例
// 循环调用本身
let p = new Promise((resolve, reject) => {
resolve(9)
})
let k = p.then(res => {
console.log(res)
return k
})
//报错: TypeError: Chaining cycle detected for promise #<Promise>
这里我们看到 我们定义了一个 k 表示 p.then 返回的 promise, 可是 我们在这个 then 里面却 返回了 k, 所以导致 调用本身产生了循环调用,接下来我们来处理这种情况
...
class MyPromise {
...
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2);
}, 0);
}
...
})
return promise2
}
}
function resolvePromise(res, resolve, reject, promise2) {
// 如果 上一个 then 返回的 res 与 then要传递的 promise相同说明 产生循环调用
if (promsie2 === res) {
// 将 错误传递给下个then 的 reject
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
...
}
// 循环调用本身
let p = new MyPromise((resolve, reject) => {
resolve(9)
})
let k = p.then(res => {
console.log(res)
return k
})
// 在 后面一个then可以获取到报错信息
k.then(res => {}, reason => console.log(reason)) // Chaining cycle detected for promise #<Promise>
这里我们给 resolvePromise 传递了一个新参数 promise2, 这个 promise2 是 then将要传递给下一个promise的, 然后在这个方法里面 我们通过 res(上个then返回值)与 promise2 比较,如果相等,则循环调用,通过 reject将循环报错信息传递给下个 promise 的 reject. 在代码中的测试我们可以看到确实获取到了报错信息, 原生的 Promise 同样也是这么做的.
上面有一个注意点: 我给那个函数加了一个 settimeout 为0, 这么做的原因是 promise2 还没有初始化完我们就拿来用会导致 报错, 所以给他加个异步机制, 这样传递就不会有问题了.
then传递的不是一个函数
then(successCallback, failCallback){
successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
...
})
我们只需要在then的头部增加这么两行代码, 判断 successCallback是不是函数, 这样 then 即使传递了一个普通值也只会将 前面的值传递 给下一个then,忽略这次then的执行 例如:
let p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then('ye ye ye')
.then('sss')
.then(res => console.log(res, 'res'), // 输出 1 res
reason => console.log(reason, 'reason'))
捕获错误信息
1 如果我们在 new Promise 里面写了一个错误代码, 那么我们应该将之捕获传递给第一个then中的 reject.
let p = new MyPromise((resolve, reject) => {
throw new Error('代码错误')
})
p.then(res => {}, reason => {console.log(reason)}) // 代码错误
具体的代码也很简单, 只需要给 构造函数中的执行器包裹一层 try catch就行
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
2 如果在 promise.then中报错,同样的方式 try catch 捕获,传递给下一个then的 reject.
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
处理 异步时候 then 链式调用
在上面的代码中我们发现其实都是在同步的状态处理 resolve, 我们先把 reject 处理一下, 处理方法和 resolve 一样
else if (this.status === REJECTED) {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
只需要将 失败回调的值传递给 resolvePromise就完成了.现在还剩下异步的没有处理,首先我们看到处理异步的代码如下, 要将之改为 能够链式调用
else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
解决方法如下
else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
}
将 successCallback 包裹在一个函数中,然后复制 同步中的代码即可
resolve = value => {
// while(this.successCallback.length) this.successCallback.shift()(value)
while(this.successCallback.length) this.successCallback.shift()()
}
最后只要将 resolve 和 reject 中传递的 value 和 reason 删除即可.这样 then的链式调用基本完成.
完整代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
resolve = value => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then = (successCallback, failCallback) => {
successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
else if (this.status === REJECTED) {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
}
})
return promise2
}
}
function resolvePromise(res, resolve, reject, promise2) {
if (promise2 === res) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// then 返回的是 promise
if (res instanceof MyPromise) {
res.then(value => resolve(value), reason => reject(reason))
}
else {
// then返回的是普通值
resolve(res)
}
}
实现静态方法 MyPromsie.resolve
看看如何使用
let p = Promise.resolve(1).then(res => {console.log(res)}) // 1
let k = Promise.resolve(new Promise((resolve) => {
resolve(333)
})).then(res => console.log(res)) // 333
我们需要处理这两种情况, 实现如下
static resolve(value) {
// 如果本身就是 promise,只需要直接返回
if(value instanceof MyPromise) return value
// 如果是一个值,将值包裹在 promise中返回
return new MyPromise((resolve) => resolve(value))
}
all 的实现
mdn 介绍: Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。 案例:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
接下来就是实现部分, 首先我们看看有点问题的 all
static all = arr => {
// 结果数组
let ans = []
// 不管怎样, all返回的都是 一个 promsie实例
return new MyPromise((resolve, reject) => {
// 遍历 传入的数组
arr.forEach(current => {
// 如果是一个 promise
if(current instanceof MyPromise) {
// 调用 then获取它实际的 值,如果是成功的就会 push进数组,
// 如果是错误的,会直接 reject,这个时候promise状态已经变为 rejected,不会再变了
current.then(res => {
ans.push(res)
//只有长度相等才会 resolve
if(ans.length === arr.length) resolve(ans)
},reason => reject(reason))
}
else {
ans.push(current)
}
});
})
}
}
首先 all 返回的是一个 promise, 所以直接返回 new MyPromise,然后就是遍历数组, 有两种情况,如果是一个 promise,我们会 分别拿 resolve和reject值, 如果是 resolve,会推入结果数组,当结果数组长度与输入的数组长度相等就 resolve(ans),不然期间有一个reject,会直接 reject一个错误出去.
const promise1 = MyPromise.resolve(3);
const promise2 = 42;
const promise3 = new MyPromise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
MyPromise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
// 实际输出: [ 42, 3, 'foo' ]
mdn中有这么一句话 返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。 我们的代码其实没有按照顺序,考虑到异步,推入的顺序确实有可能是不同的
解决办法
static all = arr => {
// 结果数组
let ans = []
// 不管怎样, all返回的都是 一个 promsie实例
return new MyPromise((resolve, reject) => {
function addData(key, value) {
ans[key] = value
if (ans.length === arr.length) resolve(ans)
}
arr.forEach((current, i) => {
if (current instanceof MyPromise) {
current.then((res) => {
addData(i, res)
}, reason => reject(reason))
}
else {
addData(i, current)
}
});
});
}
}
我们将值添加到数组的时候不用 push, 而是用 key, value的形式,这样就可以强制让每一个 promise在其适当的位置了.
catch实现
终于到了最后一个api的实现,我们知道 promise不仅仅可以用 then的第二个参数捕获错误,也可以直接通过在.then后面直接 .catch捕获错误,这样的好处是如果有多个then,我们只要在最后一个 then后面catch就行, 而catch原理也很简单, 就是类似于 then(undefind,failCallback),为什么这样可以捕获呢,因为 多个then链式调用的时候,只要有个then出错,错误就会一直传递到最后一个then中的 失败回调.
实现原理如下:
catch (failCallback) {
return this.then(undefined, failCallback)
}
最后奉上完整代码
详细代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
resolve = value => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then = (successCallback, failCallback) => {
successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
else if (this.status === REJECTED) {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
}
else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let res = successCallback(this.value)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let res = failCallback(this.reason)
resolvePromise(res, resolve, reject, promise2)
} catch (e) {
reject(e)
}
}, 0);
})
}
})
return promise2
}
catch (failCallback) {
return this.then(undefined, failCallback)
}
static resolve(value) {
// 如果本身就是 promise,只需要直接返回
if (value instanceof MyPromise) return value
// 如果是一个值,将值包裹在 promise中返回
return new MyPromise((resolve) => resolve(value))
}
static all = arr => {
// 结果数组
let ans = []
// 返回一个 promsie实例
return new MyPromise((resolve, reject) => {
function addData(key, value) {
ans[key] = value
if (ans.length === arr.length) resolve(ans)
}
arr.forEach((current, i) => {
if (current instanceof MyPromise) {
current.then((res) => {
addData(i, res)
}, reason => reject(reason))
}
else {
addData(i, current)
}
});
})
}
}
function resolvePromise(res, resolve, reject, promise2) {
if (promise2 === res) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// then 返回的是 promise
if (res instanceof MyPromise) {
res.then(value => resolve(value), reason => reject(reason))
}
else {
// then返回的是普通值
resolve(res)
}
}
总结
前端学习之 路漫漫其修远兮,吾将上下而求索!!!