本质上 Promise 是一个函数返回的对象
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
Promise是一个对象,它被用作延迟(可能是异步)计算的最终结果的占位符。
Any Promise object is in one of three mutually exclusive states: fulfilled, rejected, and pending:
任何Promise对象都处于以下三种互斥状态之一:已完成、已拒绝和待处理:
什么是回调地狱
- 多层嵌套
- 每种问题存在成功和失败的两条路径,错误处理的路径非常难受
Promise 通过什么解决回调地狱
- 回调函数延迟绑定:在 .then 中传入回调函数,而不是直接在参数里面占个位置声明回调函数
- 返回值穿透:.then 返回的还是 promise,可以继续 .then
- 错误冒泡:只需要在最后面 .catch ,前面产生的错误会自动向后传递
Promise 为什么要引入微任务?
Promise解决回调可以通过三种方式,但是采用的是最后一种:
- 使用同步回调,直到异步任务进行完,再进行后面的任务。阻塞脚步,
浪费CPU资源。 - 使用异步回调,将回调函数放在进行
宏任务队列的队尾。任务队列非常长时,回调函数迟迟无法执行,实时性不好 - 使用异步回调,将回调函数放到
当前宏任务中的最后面。
Promise 如何实现链式调用?
首先,promise 有状态state,有下面三种取值:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
然后我们需要 value 来记录应该返回的值,peomise里面是有return 结果的,需要记录下来。
我们还需要 error 来记录遇到错误时的错误信息,用以返回。
再加上成功的回调函数(.then里面调用),和失败的回调函数(.catch里面调用)。
上面这些值都是以闭包的形式记录的
- 成功和失败的回调函数记录是以数组的形式记录的,因为可以
const p1 = new Promise();,然后p1.then(...);,p1.then(...);,即可以绑定多个回调函数。
function CustomPromise(executor){
let self = this
self.value = null
self.error = null
self.status = PENDING
self.onFulfilledCallbacks = []
self.onRejectedCallbacks = []
const resolve = (value) => {
if (self.status !== PENDING) return // 只会执行第一个 resolve 或者第一个 reject
setTimeout(() => {
self.status = FULFILLED
self.value = value
self.onFulfilledCallbacks.map(cb => cb(self.value))
})
}
const reject = (error) => {
if (self.status !== PENDING) return
setTimeout(() => {
self.status = REJECTED
self.error = error
self.onRejectedCallbacks.map(cb => cb(self.error))
})
}
executor(resolve, reject) // 我们把 定义好的resolve和reject放进去,在执行后会被自动调用。
}
.then 函数是最难以理解的地方,先写一版最简单的:
- 对于传入的成功回调和失败回调,需要判断是否是函数
- 参数放入 onFulfilledCallbacks 和 onRejectedCallbacks 数组中
- 如果当前 promise 的状态已经是完成时,则不需要放入,直接执行即可
CustomPromise.prototype.then = function(onFulfilled, onRejected) {
// 成功回调不传,给它一个默认函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
// 对于失败回调直接抛错
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
} else if (this.status === FULFILLED) {
onFulfilled(this.value);
} else {
onRejected(this.error);
}
return this;
}
这样存在一个问题,因为返回的是 this,而且是直接返回的,其实这个时候是无法做到链式调用的
const res = {
aaa: {
name: 'aaa'
},
bbb: {
name: 'bbb'
}
}
const generatePromise = (str) => {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
console.log(str)
resolve(res[str])
}, 1000)
})
}
generatePromise('aaa').then((res1) => {
console.log(res1)
return generatePromise('bbb')
}).then((res2) => {
console.log(res2)
})
// aaa
// {name: 'aaa'}
// {name: 'aaa'} // 立即打印了出来,因为 .then 里面 return this(第一个promise),然后把第二个.then 的回调函数也放到第一个promise的 onFulfilledCallbacks 里面了,所以在 generatePromise('aaa') 执行完后,立即触发了两个回调函数
// bbb
所以我们需要修改返回值,首先修改 PENDING 状态时的返回值
- 通过返回一个新的 Promise 实现,我们叫他 bridgePromise
- 第二个 .then 的回调函数会放到这个 bridgePromise 的 onFulfilledCallbacks 里面
- 那么我们就应该往初始的 promise 里面放入一个回调函数,在这个回调函数里面触发 bridgePromise 的 resolve,修改 bridgePromise 的状态为完成,从而触发 bridgePromise 的 onFulfilledCallbacks,里面就有第二个 .then 的回调函数
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
let bridgePromise;
let self = this;
if (self.status === PENDING) {
return bridgePromise = new CustomPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
// 看到了吗?要拿到 then 中回调返回的结果。
let x = onFulfilled(value);
resolve(x);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolve(x);
} catch (e) {
reject(e);
}
});
});
}
//...
}
但是还有问题,主要是 then 中回调返回的结果可能又是个 Promise,这个时候我们没有处理,所以不能直接 resolve(x),即 x 是个 promise
要创建一个新的 resolve 来处理他
function resolvePromise(bridgePromise, x, resolve, reject) {
//如果x是一个promise
if (x instanceof CustomPromise) {
// 拆解这个 promise ,直到返回值不为 promise 为止
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgePromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject); // x已经完成,.then 会直接触发执行 resolve 和 reject
}
} else {
// 非 Promise 的话直接 resolve 即可
resolve(x);
}
}
其中,resolvePromise 中的参数 resolve 和 reject 始终都是第一个 Promise 的 resolve 和 reject,他们会控制第一个传入的 bridgePromise 的状态。
如果状态不是 PENDING,也需要做响应的处理
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 防止不传参数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error }
let bridgePromise
let self = this
if (this.status === PENDING) {
return bridgePromise = new CustomPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error)
resolve(x)
} catch (e) {
reject(e)
}
})
})
} else if (this.status === FULFILLED) {
return bridgePromise = new CustomPromise((resolve, reject) => {
try {
// 状态变为成功,会有相应的 self.value
let x = onFulfilled(self.value)
// 暂时可以理解为 resolve(x),后面具体实现中有拆解的过程
resolvePromise(bridgePromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.status === REJECTED) {
return bridgePromise = new CustomPromise((resolve, reject) => {
try {
// 状态变为失败,会有相应的 self.error
let x = onRejected(self.error)
resolvePromise(bridgePromise, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
}
这个时候,上面的例子就能正常执行了
const res = {
aaa: {
name: 'aaa'
},
bbb: {
name: 'bbb'
}
}
const generatePromise = (str) => {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
console.log(str)
resolve(res[str])
}, 1000)
})
}
generatePromise('aaa').then((res1) => {
console.log(res1)
return generatePromise('bbb')
}).then((res2) => {
console.log(res2)
})
// 1秒钟后打印 aaa,然后返回 res1 {name: 'aaa'} 并打印
// aaa
// {name: 'aaa'}
// 再过一秒钟后打印 bbb,然后返回 res2 {name: 'bbb'} 并打印
// bbb
// {name: 'bbb'}
如果 我们在 .then 方法里面打印当前的this,会发现在一开始就打印出来两个 CustomPromise,这两个分别是 generatePromise('aaa').then 以及再 .then 中生成的新的 Promise(匿名的 return bridgePromise = new CustomPromise... )
如果我们给 CustomPromise 编号,并在生成时打印出来,上面的代码一共会生成 5 个 Promise
- 调用 generatePromise('aaa') 生成了 0 号 Promise
- 调用 generatePromise('aaa').then 生成了 1 号 Promise(bridgePromise),把打印 res1 的回调函数放入其中
- 调用 generatePromise('aaa').then().then 生成了 2 号 Promise(bridgePromise),把打印 res2 的回调函数放入其中
- 开始执行 generatePromise('aaa') 里面的异步函数,执行完毕,触发 resolve(),执行打印 res1 的回调函数
- 开始 return generatePromise('bbb'),此时调用了 generatePromise('bbb'),又生成了 3 号 Promise
- 因为返回的是 Promise,在 1 号 Promise 中,x 是Promise,就需要使用 .then,再生成 4 号 Promise (也是 bridgePromise)来处理
let x = onFulfilled(value) // return generatePromise('bbb') 新生成的 Promise
resolvePromise(bridgePromise, x, resolve, reject)
function resolvePromise(bridgePromise, x, resolve, reject) {
console.log('调用了 resolvePromise', bridgePromise, x.status)
//如果x是一个promise
if (x instanceof CustomPromise) {
// 拆解这个 promise ,直到返回值不为 promise 为止
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgePromise, y, resolve, reject)
}, error => {
reject(error)
})
} else {
x.then(resolve, reject)
}
catch
catch 方法其实是 then 方法的语法糖
CustomPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
因为 then 方法中,
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
所以,一旦其中有一个PENDING状态的 Promise 出现错误后状态必然会变为失败, 然后执行 onRejected函数,而这个 onRejected 执行又会抛错,把新的 Promise 状态变为失败,新的 Promise 状态变为失败后又会执行onRejected......就这样一直抛下去,直到用catch 捕获到这个错误,才停止往下抛。
Promise.resolve
主要看传参
- 传参为一个 Promise, 直接返回它。
- 传参为一个 thenable 对象,返回的 Promise 会跟随这个对象,
采用它的最终状态作为自己的状态。 - 其他情况,直接返回以该值为成功状态的 promise 对象。
CustomPromise.resolve = (param) => {
if(param instanceof CustomPromise) return param;
return new CustomPromise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}
Promise.reject
Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下:
CustomPromise.reject = function (reason) {
return new CustomPromise((resolve, reject) => {
reject(reason);
});
}
Promise.finally
CustomPromise.prototype.finally = function(callback) {
this.then(value => {
return CustomPromise.resolve(callback()).then(() => {
return value;
})
}, error => {
return CustomPromise.resolve(callback()).then(() => {
throw error;
})
})
}
Promise.all
对于 all 方法而言,需要完成下面的核心功能:
- 传入参数为一个空的可迭代对象,则直接进行 resolve。
- 如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败。
- 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个
数组
CustomPromise.all = function(promises) {
return new CustomPromise((resolve, reject) => {
let result = [];
let index = 0;
let len = promises.length;
if(len === 0) {
resolve(result);
return;
}
for(let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
CustomPromise.resolve(promise[i]).then(data => {
result[i] = data;
index++;
if(index === len) resolve(result);
}).catch(err => {
reject(err);
})
}
})
}
Promise.race
只要有一个 promise 执行完,直接 resolve 并停止执行。
CustomPromise.race = function(promises) {
return new CustomPromise((resolve, reject) => {
let len = promises.length;
if(len === 0) return;
for(let i = 0; i < len; i++) {
CustomPromise.resolve(promise[i]).then(data => {
resolve(data); // 只有最先的 resolve 会改变上面 new Promise 的状态,后续的尝试性变更会因为 status !== PENDING,直接 return
return;
}).catch(err => {
reject(err);
return;
})
}
})
}
小结
以上是 ES6 的 promise 的 api 实现,在后续发展中
- ES8 引入了 async、await
- ES11 引入了 allSettled、any
- ES12 改进了 Promise.resolve() 和 Promise.reject(),引入了 AggregateError 类(一次操作中处理多个错误)
然后我继续来实现这些效果
Promise.any 只要其中的一个 Promise 成功解决,就返回那个已经成功的 Promise,否则返回全部错误结果
分析需求:
- 接受参数是数组,不是数组要报错
- 返回一个新的 Promise,遍历处理参数数组
- 处理时先通过 Promise.resolve 包装一下,以免参数不是 promise
- 在遇到一个成功的情况,直接 resolve
- 计数错误数量,如果达到参数数组的长度,说明全部失败,返回全部错误信息
- 如果不支持表示多个错误信息的
AggregateError,需要自己实现这个内置的错误对象
CustomPromise.any = function (promises) {
return new CustomPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError('Argument must be an array') // 校验参数
}
let rejectedCount = 0 // 统计错误个数
let errors = [] // 记录错误信息
promises.forEach((promise, index) => {
CustomPromise.resolve(promise).then(
value => {
resolve(value) // 一旦有一个 promise 解决,立即返回此 promise
},
error => {
rejectedCount++
errors[index] = error
if (rejectedCount === promises.length) {
// 全部失败时,返回全部失败的错误信息
reject(new AggregateError(errors, 'All promises were rejected'))
}
})
})
})
}
还是拿上面的做例子
const res = {
aaa: {
name: 'aaa'
},
bbb: {
name: 'bbb'
}
}
const generatePromise = (str, time) => {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
console.log(str)
if (res[str]) {
resolve(res[str])
} else {
reject(new Error('can not find param: ', str))
}
}, time)
})
}
CustomPromise.any([generatePromise('aaa', 1000), generatePromise('bbb', 2000), generatePromise('ccc', 500)]).then(res => {
console.log('promise.any excute success, result is ', res)
}).catch(error => {
console.log('promise.any excute fail, error is ', error)
})
// ccc // 500毫秒后打印 ccc,放入错误数组
// aaa // 1秒后打印 aaa,执行成功,触发 resolve
// promise.any excute success, result is {name: 'aaa'} // 执行 any 的 then 回调
// bbb // 2秒后打印 bbb,执行成功,但 resolve不会再触发
修改测试用例,打印错误信息
CustomPromise.any([generatePromise('aaaa', 1000), generatePromise('bbbb', 2000), generatePromise('ccc', 500)]).then(res => {
console.log('promise.any excute success, result is ', res)
}).catch(error => {
console.log('promise.any excute fail, error is ', error)
error.errors.forEach((err, index) => {
console.error(`Error ${index + 1}:`, err)
})
})
// ccc // 500毫秒后打印 ccc,放入错误数组
// aaaa // 1000毫秒后打印 aaaa,放入错误数组
// bbbb // 2000毫秒后打印 bbbb,放入错误数组
// promise.any excute fail, error is AggregateError: All promises were rejected // 全部执行错误
// at this.html:153:24
// at this.html:87:23
// at this.html:41:46
// at Array.map (<anonymous>)
// at this.html:41:36
// 打印三个错误
简单的 AggregateError polyfill
class AggregateErrorPolyfill extends Error {
constructor(errors, message) {
super(message);
this.errors = errors;
this.name = 'AggregateError';
}
}
使用
try {
throw new AggregateErrorPolyfill([
new Error('Error 1'),
new Error('Error 2'),
new Error('Error 3')
], 'All promises were rejected');
} catch (error) {
if (error instanceof AggregateErrorPolyfill) {
console.error('Caught an AggregateError:', error.message);
error.errors.forEach((err, index) => {
console.error(`Error ${index + 1}:`, err.message);
});
} else {
console.error('Caught an unexpected error:', error);
}
}
Promise.allSettled
在所有输入的 Promise 都被解决后返回一个 Promise,无论这些 Promise 是成功解决还是被拒绝。
分析需求:
- 参数校验,需要是数组
- 完成个数记录,在成功回调和失败回调中,都记录结果
- 如果个数等于参数数组个数,返回结果,只有 resolve,不会 reject
- 结果是个对象,status表明状态,成功就是value的值,失败就是reason的错误原因
- 其余的和 any 很像
CustomPromise.allSettled = function (promises) {
return new CustomPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError('Argument must be an array') // 校验参数
}
let completedCount = 0 // 统计个数
let results = [] // 记录结果
promises.forEach((promise, index) => {
CustomPromise.resolve(promise).then(
value => {
completedCount++
results[index] = { status: 'fulfilled', value }
if (completedCount === promises.length) {
resolve(results)
}
},
reason => {
completedCount++
results[index] = { status: 'rejected', reason }
if (completedCount === promises.length) {
resolve(results)
}
})
})
})
}
面试题汇总
- 如果要你实现一个 Promise.async 方法,参数是一个 promise 数组,然后其中的 promise 会同步执行,该怎么实现?
这题我想了很久,然后发现很奇怪,因为参数是 promise 数组,而我们的 all,race 等,其实在参数里面已经开始运行了,只不过我们在 all 方法里面通过同步执行的代码,立即生成了很多的回调函数而已,然后这些 promise 执行完之后,才会一个个触发回调函数,所以根本不可能实现什么同步运行,promise 是同步启动的,但是执行是异步执行的,我们的 .then 再 .then ,也只是在第一个 promise 执行完的回调函数里面再生成第二个 promise 才实现的。
我找了一个类似的可能符合面试官要求的答案,他的参数是生成 promise 的参数,然后在内部再生成 promise,但是我认为这个题目是有歧义的,无意义的
CustomPromise.async = async (arr) => {
if (!Array.isArray(arr)) {
throw new TypeError('Argument must be an array') // 校验参数
}
let p = CustomPromise.resolve()
arr.forEach((params, index) => {
p = p.then(_ => new CustomPromise((resolve) => {
resolve(generatePromise(...params)) // 在回调函数里面再生成 promise,否则立即就会执行
}))
})
return CustomPromise.resolve(p)
}
CustomPromise.async([['aaa', 1000], ['bbb', 2000], ['ccc', 500]]).then(res => {
console.log(res)
}).catch(error => {
console.log(error)
})
参考
作者:神三元 链接:juejin.cn/post/684490… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。