关于对Promise的一些理解
最近在准备春招,在对各种Promise方法进行手写复习的时候,闲着无聊,也为了能在面试的时候能说出更多东西提升一下面评,我进行了很多样例测试。不测不知道,一测吓一跳:
怎么这么多bug!以前认为理所当然的事情原来讲究这么多!
就拿下面的 Promise.all
来讲吧!
Promise.all
没多想,一开始我是直接使用遍历加push实现,但是我发现,如果队列存在异步,则会出现不同的结果!
Promise.all
接收一个可迭代对象,函数内部会用 Promise.resolve()
将所有成员封装成Promise,如果都是可完成的状态,则会按顺序传入的顺序添加进数组并返回。
Promise.all([
new Promise(resolve => setTimeout(resolve.bind(null, 1), 1000)),
Promise.resolve(2),
3,
]).then(res => console.log(res)) // [1,2,3]
如果传递的参数存在被拒绝的情况reject,则会返回该 Promise
被拒绝的原因。
有了前面按顺序返回结果的情况,我理所应当的认为,错误也是按传入顺序返回的,结果大跌眼镜。
Promise.all([
new Promise((resolve, reject) => setTimeout(reject.bind(null, 1), 1000)),
Promise.reject(2)
]).catch(e => console.log(e)) // 2
源码撕半天最后得出:如果参数中有多个 Promise 被拒绝,Promise.all() 返回最早被拒绝的 Promise 的错误信息。
最终实现:(也可以用async,await实现,就比较简单一点)
PromiseA+ 的理解
看一下定义:
-
promise应该有三个状态:
- pending(初始状态可变)、
- fulfilled(最终态不可变、一个promise被resolve后变成该状态、必须拥有一个value值)、
- rejected(最终态不可变、一个promise被reject后变成该状态(不是throw Error:直接报错)、必须拥有reason值)
-
promise应该有个then方法,用来访问最终结果(value or reason),而且then返回的应该是一个Promise对象
Promise.then(onFulfilled, onRejected) // 参数若不是函数则被忽略
-
onFulfilled和onRejected应该是微任务
- 在执行上下文堆栈仅包含平台代码之前,不得调onFulfilled 或 onRejected函数,onFulfilled 和 onRejected 必须被作为普通函数调用(即非实例化调用,这样函数内部 this 非严格模式下指向 window),使用queueMicrotask或者setTimeout来实现微任务的调用
- then可被调用多次
- then返回一个promise对象
记不清吧!且看我细细道来:
我们从下面问题来进行对 PromiseA+ 规范的学习:
在 Promise
内部多次 resolve()
,返回的是第一个成功的结果还是最后一个?(为啥??)
new Promise(resolve => {
resolve(1)
resolve(2)
}).then(res => console.log(res)) // 1
这是因为Promise的状态一旦变为resolved或rejected,就无法再次改变,
Promise内部的任务都是异步执行的,因此无论是resolve还是reject,都需要等到任务队列中的其他任务执行完毕后才会被执行。 如果在循环中同时调用了多次resolve和reject方法,它们会被加入到任务队列中,最终会按照任务队列中的顺序执行。
如何中断 Promise
的请求?
- then()接受两个参数 如果参数是不是函数将被忽略
- onFulfilled 将在promise fulfilled 后调用并接受一个参数
- onRejected 将在promise rejected 后调用 并接受一个参数
- 另外then一定返回的是promise, 且原 promise的状态会与 新promise的状态保持一致
答案呼之欲出,中断 Promise 请求,只需要在 then 返回新的 promise 对象的时候把该对象的状态变为 pending 不就可以!
/*
* 中断promise,也就是在then将返回值新promise保持状态为pending
* 那么这个promise的链也会中断(等待)
*/
let bool = false;
Promise.resolve('哈哈哈哈哈我运行了').then((res) => {
if(bool) return new Promise((resolve, reject) => {});
return res;
}).then((res) => {
console.log(res)
})
bool = true;
Promise.resolve
、 Promise.reject
是如何实现的?
Promise面试题常考代码输出,如下:
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4)
})
// return Promise.resolve() 相当于 new Promise((resolve) => resolve(4)).then(res => res)
.then((res) => {
console.log(res)
});
Promise.resolve()
.then(() => {
console.log(1)
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(3)
})
.then(() => {
console.log(5)
})
// 0 1 2 3 4 5
为什么是 0 1 2 3 4 5?我们从源码入手了解一下就简单了。
简单实现 Promise.resolve
和 .reject
对于Promise.resolve来说,就得考虑多种情况了,而Promise.reject直接返回错误原因就可以了。
话不多说,直接贴代码:
/**
* 如果参数已经是promise,则直接返回
* 如果是thenable对象,则包装成promise再返回
* {
* then(resolve) {
* setTimeout(resolve)
* }
* }
* @param {*} prom
*/
Promise._resolve = function(prom) {
if(prom instanceof Promise) return prom;
if(prom && typeof parm.then === 'function') {
return new Promise((resolve, reject) => {
prom.then(resolve, reject);
})
}
return new Promise((resolve, reject) => {
resolve(prom);
});
}
Promise._reject = function(reason) {
return new Promise((_, reject) => {
reject(reason);
})
}
Promise._resolve(Promise._reject('错误啦')).catch(err => {
console.log(err);
})
回到前面:
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4)
})
这里就相当于三次微任务操作,分别是第一次的Promise.resolve(),第二次的then的执行,最后是最后一个Promise.resolve的执行并返回该新promise状态。
Promise.catch
前面说了 Promise.then 支持俩个函数作为参数,第二个参数表示对错误的拒绝,并将 Promise 状态改为 rejected。
而这里的Promise.catch,本质上就是一个语法糖,只是对then的第二个参数进行包装
/**
* 语法糖
* 本质就是then,只是少传了一个onFulfilled
* 所以仅处理失败的场景
* @param {*} param
*/
Promise.prototype._catch = function(fn) {
return this.then(null,fn);
}
问题来了:使用 Promise.catch 和 使用 then 的 onFulilled 也就是第二个函数参数处理错误对象,哪个更快?还是一样快?
const p = Promise.reject(1)
const p2 = Promise.reject(2)
new Promise((resolve, reject) => {
Promise.resolve(p).then(res => resolve(res)).catch(rej => reject(rej))
}).then(null, rej => console.log(rej))
new Promise((resolve, reject) => {
Promise.resolve(p2).then(res => resolve(res), rej => reject(rej))
}).then(null, rej => console.log(rej))
// 2 1
答案会输出 2 1,是的,Promise.then的第二个函数会比使用catch快。
原因很简单:多执行了一次then嘛。
总结
所以,在进行对Promise各种方法的手写的时候,要多考虑可能发生的情况,多进行异步的测试。才能更熟悉的理解promise,面试问Promise的时候直接跟手撕面试官。
就算我们站在群山之颠,也别忘记雄鹰依旧能从我们头顶飞过。骄傲是比学习前端JAVASCRIPT更可笑的东西。
不要停止学习!
转载自:https://juejin.cn/post/7206909221806440503