likes
comments
collection
share

关于对Promise的一些理解

作者站长头像
站长
· 阅读数 8

最近在准备春招,在对各种Promise方法进行手写复习的时候,闲着无聊,也为了能在面试的时候能说出更多东西提升一下面评,我进行了很多样例测试。不测不知道,一测吓一跳:

关于对Promise的一些理解

怎么这么多bug!以前认为理所当然的事情原来讲究这么多!

就拿下面的 Promise.all 来讲吧!

Promise.all

没多想,一开始我是直接使用遍历加push实现,但是我发现,如果队列存在异步,则会出现不同的结果!

关于对Promise的一些理解

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 的错误信息。

关于对Promise的一些理解

最终实现:(也可以用async,await实现,就比较简单一点)

关于对Promise的一些理解

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对象

记不清吧!且看我细细道来:

关于对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.resolvePromise.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快。

关于对Promise的一些理解

原因很简单:多执行了一次then嘛。

总结

所以,在进行对Promise各种方法的手写的时候,要多考虑可能发生的情况,多进行异步的测试。才能更熟悉的理解promise,面试问Promise的时候直接跟手撕面试官

就算我们站在群山之颠,也别忘记雄鹰依旧能从我们头顶飞过。骄傲是比学习前端JAVASCRIPT更可笑的东西。

不要停止学习!

关于对Promise的一些理解