一个面试知识块---Promise

257 阅读8分钟

这里主要记录了promise的常考问题(在最后给出了promise的手写),根据聊promise的思路简单记了记,可能有地方聊错了,或者有知识点遗漏,欢迎大家讨论纠正。


1. 了解 promise 吗?

了解

  • 首先promise是一种异步编程解决方案,有三种状态,pending(进行中)、resolved(已完成)、rejected(已失败),当promise的状态由pending转为resolved或rejected时,会执行对应的方法。状态一旦发生改变,就无法返回。
  • Promise的提出主要就是为解决回调地狱。

2. promise 解决的痛点是什么?

  1. 回调地狱
  2. 支持多并发的请求(promise.all)
  3. 解决了可读性差的代码问题
  4. promise 解决了代码的信任问题,promise 只有一次决议(状态只有一个)

3. promise 解决的痛点还有其他解决方法吗?

setTimeout      缺点(它只能保证在一段时间后将一段代码加入到任务队列中去,并不能保证在合适的时间将这段代码执行)

async、await     缺点(async、await没有错误捕捉机制,需要手动写try/catch)

generator       缺点(虽然可以将异步函数的整体表示得很简洁(疯狂 yield 接异步),但是整体流程管理不方便,需要手动控制流程)

回调函数

4. promise 如果使用

  1. 创建 promise 的实例对象
  2. 用 .then 方法指定 resolved 状态和 rejected 状态的回调函数
  3. 用 catch 方法指定 rejected 状态的回调函数

5. promise 存在的问题?解决方法?

  1. promise 一旦执行,无法中途取消

  2. promise 的错误无法在外部捕捉到

    • 借助try catch实现错误捕捉
  3. promise 的内部如何运行很难被监测(因为promise 的封装算是蛮封闭的了)

    • 解决办法

    async、await

6.老旧的浏览器没有 promise 全局对象怎么办?

用轮子 es6-polufill 可以用一个页面标签引入,或者用ES6的 import 去引入这个插件,

只要引入这个后,它就会在 window 中加入 promise 对象,这样就可以全局使用 promise 了.

否则就只能用其他异步方法去实现相同功能了。

7. 怎么让一个函数无论 promise 对象成功还是失败都能被调用

Promise.finally()

8. 分别介绍一下Promise.all() Promise.allSettled() Promise.race()方法

Promise.all() 将多个promise实例,包装成一个新的promise实例 它的执行结果方面:传入的实例执行结果全为resolve则返回resolve给调用者的成功回调,反之一次全错,直接调用失败回调。 Promise.allSettled() 只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更,一旦发生状态变更,状态总是fulfilled,不会变成rejected。

> 要将它与all方法区分开来,all会在一个发生错误时,就会执行reject,而allSettled会等到全部执行完毕,并且一定是成功回调

Promise.race() race 方法,返回的是一个Promise对象,由第一个返回的Promise方法决定的 这个方法主要用来优化,例如:两种方法请求数据,哪种方法先返回数据,就拿着数据执行接下来的逻辑

9. promise的实现(拓展)

其实面试的时候不会叫你完整的写一个promise,只是说写这个能加深自己对于promise的理解。

注: 面试方面(常考点):手写一个Promise.all()、Promise.then()的实现思路

(function (window) {         //写一个自执行函数,将这个 Promise 挂在 window 上
    function myPromise(execotor) {        // execotor 执行器函数           
        //注意看实例对象的使用,会发现 execotor 就是Promise里的箭头函数
        // 绑定作用域
        let self = this
        // 开关变量,记录 Promise执行到何种状态
        self.status = 'pending'
        //用来存储 resolve  的结果
        self.data = undefined
        //用来放上 一个又一个的 {resolve =>{},reject =>{}} 
        self.callbacks = []
        // 延时绑定  延时执行!!!!!!!!!!

        // 因为promise函数对象的特点是:如果对象还没有返回状态(resolve/reject)的时候,会将then里的后续代码保存,只要返回了状态,就会执行状态对应的相关代码



        // 向 Promise 的原型对象上添加方法  
        function resolve(value) {
            if (self.status !== 'pending') {
                //说明状态已经改变
                return
            }

            // 将状态改变成为 resolved
            self.status = 'resolved'

            // 存值
            self.data = value

            // 注意!!!! .then里面的resolve是一个箭头函数!不会自动调用!
            // 如果有待执行的 callback函数,立即 异步执行 它
            if (self.callbacks.length > 0) {
                // 这里用一个setTimeout简单实现一下异步执行
                setTimeout(() => {
                    self.callbacks.forEach(callbackObj => {          //callbackObj  拿到的应该是这种对象   {resolve =>{},reject =>{}} 
                        callbackObj.onResolved(value)        //执行第一个代指resolve的函数

                    })
                }, 0)
            }
            // 接下来就要等着 .then()的执行
        }


        //reject大体和resolve是一样的 
        function reject(value) {
            if (self.status !== 'pending') {
                return
            }
            // 将状态改变成为 rejected
            self.status = 'rejected'
            // 存值
            self.data = value

            // 注意!!!! .then里面的 rejected 是一个箭头函数!不会自动调用!
            // 如果有待执行的 callback函数,立即 异步执行 它
            if (self.callbacks.length > 0) {
                // 这里用一个setTimeout简单实现一下异步执行
                setTimeout(() => {
                    self.callbacks.forEach(callbackObj => {          //callbackObj  拿到的应该是这种对象   {resolve =>{},reject =>{}} 
                        callbackObj.onRejected(value)        //执行第一个代指 rejected 的函数
                    })
                }, 0)
            }
        }

        // 构造器函数

        // 如果在执行过程中,代码出现问题保存,那么该Promise对象会直接变成 rejected 状态
        // 执行器里面使用try catch 进行捕获错误

        try {
            execotor(resolve, reject)
        } catch (error) {
            reject(error)
        }


    }


    // Promise  原型上挂载各种方法
    myPromise.prototype.then = function (onResolved, onRejected) {       //接收两个参数   onResolved函数处理成功情况,onRejected函数 处理失败情况


        // 不能直接简单粗暴的push方法进去
        // onResolved    onRejected  存到数组
        // this.callbacks.push({ onResolved, onRejected })


        // 考虑执行到 .then 的时候,Promise的状态还是pending,
        let self = this

        // 注意!这里这样写是因为   .then 要返回一个Promise对象
        return new myPromise((resolve, reject) => {
            if (self.status === 'pending') {
                this.callbacks.push({           // 延时绑定  延时执行!!!!!
                    onResolved() { onResolved(self.data) },
                    onRejected() { onRejected(self.data) }
                })
            } else if (self.status === 'resolved') {

                // 实现异步执行
                setTimeout(() => {
                    const result = onResolved(self.data)
                    // 判断.then返回的是不是一个Promise对象
                    if (result instanceof myPromise) {
                        //说明回调是一个Promise对象
                        //实现.then()的链式调用,启后
                        result.then(
                            //这里面对本次.then的resolve的调用说明了 承前
                            value => { resolve(value) },
                            reason => { reject(reason) }
                        )
                    } else {
                        resolve(result)
                    }
                }, 0)
            } else {
                setTimeout(() => {
                    onRejected(self.data)
                }, 0)
            }
        })
    }


    //一些挂载在Promise原型上的其他方法
    myPromise.prototype.catch = function (onRejected) {


    }


    // Promise 函数对象上挂载方法
    //上面的resolve和reject代指的是promise语法里的状态,而下面的则是promise对象上的具体方法
    //效果:将一个现有对象转化为Promise对象
    myPromise.myResolve = function (value) {
        // resolve方法的参数有三种可能:promise对象、thenable对象(带有then方法的对象)、其他


        // 是Promise实例,直接返回即可
        if (value && typeof value === 'object' && (value instanceof Promise)) {
            return value
        }
        // 否则其他情况一律再通过Promise包装一下 
        // 注:thenable情况下,会将传入对象转为promise对象,并立即执行其then方法
        return new Promise((resolve) => {
            resolve(value)
        })
    }


    // reject实现相对简单,只要返回一个新的Promise,并且将结果状态设置为拒绝就可以
    myPromise.myReject = function (value) {
        return new Promise((_, reject) => {
            reject(value)
        })
    }

    //all方法, 将多个 Promise 实例,包装成一个新的 Promise 实例
    //全为resolve则返回resolve给调用者的回调函数,反之一个错则全错
    myPromise.myAll = function (value) {
        return new myPromise((rs, rj) => {
            // 计数器
            let count = 0;
            // 存放结果
            let result = [];
            const len = value.length

            if (len === 0) {
                return resolve([])
            }
            value.forEach((p, i) => {
                // 注意有的数组项可能不是promise,需要手动转化一下
                Promise.resolve(p).then((res) => {
                    count += 1
                    // 收集每个Promise的返回值
                    result[i] = res
                    // 当所有的promise都成功了,那么将返回的promise结果设置为result
                    if (count === len) {
                        rs(result)
                    }
                }).catch(rj)
                // 监听数组项中的Promise   catch只要有一个失败,那么我们自己返回的promise也会失败
            })
        })
    }


    // allSettled,只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),
    //返回的 Promise 对象才会发生状态变更,一旦发生状态变更,状态总是fulfilled,不会变成rejected

    //要将它与all方法区分开来,all会在一个发生错误时,就会执行reject,而allSettled会等到全部执行完毕,并且一定是成功回调
    myPromise.myAllSettled = function (value) {
        //在上面all上微改
        return new Promise((rs, rj) => {
            let count = 0
            let result = []
            const len = value.length
            // 数组是空的话,直接返回空数据
            if (len === 0) {
                return rs([])
            }

            value.forEach((p, i) => {
                Promise.resolve(p).then((res) => {
                    count += 1
                    // 成功属性设置 
                    result[i] = {
                        status: 'fulfilled',
                        value: res
                    }

                    if (count === len) {
                        rs(result)
                    }
                }).catch((err) => {
                    count += 1
                    // 失败属性设置 
                    result[i] = {
                        status: 'rejected',
                        reason: err
                    }

                    if (count === len) {
                        rs(result)
                    }
                })
            })
        })
    }




    // race 方法,返回的是一个Promise对象,由第一个返回的Promise方法决定的
    // 这个方法主要用来优化,例如:两种方法请求数据,哪种方法先返回数据,就拿着数据执行接下来的逻辑
    myPromise.myRace = function (value) {
        return new Promise((rs,rj)=>{
            value.forEach(p=>{
                // 对p进行一次包装,处理非promise对象

                // 对其进行监听,一旦成功就执行then,并且执行我们的成功回调
                //这样就实现了,调用者的状态和先执行完的promise对象之前的关联
                Promise.resolve(p).then(rs).catch(rj)
            })         
        })
    }


    // 向外面暴露这个Promise , 这样别的地方都能用 Promise
    window.myPromise = myPromise
})()