小言_互联网的博客

【逐点突破系列】前端面试必备——异步(Promise)

236人阅读  评论(0)

1.引言

通过手写符合A+规范的promise,来深入了解Promise,再结合相关面试题,争取做到在面试的时候,如果问Promise,咱们能全方位吊打面试官😁😁😁
下面的每一个写法都对应Promise的一些特性,不断升级,了解原理后再做题就会发现很简单了

2.极简版promise

2.1 基础特性

详细介绍的话大家去看 阮一峰es6-promise,我这里当你已经有一定的基础了,然后我们总结一下基本特性

new Promise((resolve,reject)=>{ //excutor
    setTiemout(()=>{
        resolve(1) //resolve中的值会传递到成功的回调函数参数中
    },1000)
}).then((val)=>{ //onFulfiled
    console.log(val)
},(e)=>{  //onRejected
    console.log(e)
})
  1. Promise对象初始状态值为pending
  2. 立即执行excutor,在excutor中可以通过resolve,reject方法改变promise状态,分别改为filfiled(成功)和rejected(失败)
  3. 状态一旦改变状态就凝固了,无法再变
  4. then方法中的回调函数会在状态改变后执行,成功调成功回调,失败调用失败回调
  5. resolve中的值会传递到成功的回调函数参数中 (失败类似)

2.2实现

思路:上述功能点1、2、3、5都比较好实现,4的话采用发布订阅模式也能实现

class Promise {
    constructor(executor) {
        this.status='pending' //三状态
        this.value = undefined //参数
        this.reason = undefined
        this.onFulfilled = [] //发布订阅中储存回调
        this.onRejected = []
        let resolve = (value)=>{
            if(this.status==='pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onFulfilled.forEach(fn=>fn(this.value))  //发布订阅模式,异步一改变状态则立即执行回调
            }

        }
        let reject = (reason)=>{
            if(this.status==='pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejected.forEach(fn=>fn(this.reason))
            }
        }
        try{
            executor(resolve,reject)  //executor同步执行
        }catch (e) {
            reject(e)
        }

    }

    then(onFulfilled, onRejected) { // 如果then的时候 根据当前状态执行成功或者失败
        if(this.status==='fulfilled'){
            onFulfilled(this.value)
        }
        if(this.status==='rejected'){
            onRejected(this.reason)
        }
        if(this.status==='pending'){
            this.onFulfilled.push(onFulfilled) //发布订阅模式储存异步回调
            this.onRejected.push(onRejected)
        }
    }

}

3.添加链式调用

3.1 链式特性

1.如果promise中的then方法,无论是成功还是失败,他的返回结果是一个普通的时候就会把这个结果传递给外层的then的下一个then的成功回调

Promise.reject().then((val)=>{
    return 'ok'
},()=>{
    return 'err'
}).then((val)=>{
    console.log('ok' + val)
},(e)=>{
console.log('err' + e)
})
// okerr      第一个then失败的回调返回的是普通值,还是走第二个的then中成功回调

2.如果成功或者失败的回调的返回值 返回是一个promise 那么会让这个promise执行 采用他的状态

Promise.resolve().then(()=>{
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(1)
        },1000)
    })
}).then((val)=>{
    console.log(val)
})
//一秒后打印1

3.2实现

这一版主要是实现链式调用,稍微绕一点,但是理清楚了也不难
首先明确一下,then后面会返回一个新的Promise,所以才能执行链式调用
第一个比较绕的地方,怎么让第二个then里面的回调执行?只要调用then返回的新promise(promise2)时的resolve方法就行了
第二个比较绕的地方就是参数是什么?我们看特性3.1,参数是什么要根据第一个then中回调的返回值来判断,返回值如果是正常值,如果是Piomise,,所以我们封装一个resolvePromise的方法来处理,参数的话有第一个then的回调,新创建的promise2,以及promise2里面的resolve.reject

需要改变的核心代码如下
let resolvePromise = (promise2, x, resolve, reject) => {...}

class Promise {
    construcotr(){...}
    then(){
        let promise2 =  new promise((resolve,reject)=>{
            let x = onFulfiled() // onFulfilef是第一个then中的回调函数
            resolvePromise(promise2,x, resolve, reject)
        })
        return promiese2
    }
}

resolvePromise这个方法会判断onFulfiled返回值类型,如果是普通值会怎么样,如果是一个Promise会怎么样,如果报错会怎么样,详细实现方法可以参考promise A+规范
完整实现

let resolvePromise = (promise2, x, resolve, reject) => {
    // 监测到环形链
    if(promise2===x) return new TypeError('chaining cycle detected for promise')
    if(typeof x ==='function' ||(typeof x ==='object' && x!==null)){
        try{
            //尝试取出then,有问题报错
            let then = x.then
            if(typeof then === 'function'){ //这里是最绕的,想清楚promise2和x的关系,x.then会不会执行取决于使用者的逻辑,会不会在第一个then中回调函数中返回的promise中调用它的resolve改变状态
                then.call(x,resolve,reject)
            }else{// then不是function
                resolve(x)
            }
        }catch (e) {
            reject(e)
        }
    }else{ //普通类型
        resolve(x)
    }

}

class Promise {
    constructor(executor) {
        this.status = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallback = []
        this.onRejectedCallback = []
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.status = 'fulfilled'
                this.value = value
                this.onFulfilledCallback.forEach(fn => fn(this.value))
            }

        }
        let reject = (reason) => {
            if (this.status === 'pending') {
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallback.forEach(fn => fn(this.reason))
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }

    }

    then(onFulfilled, onRejected) { // 如果then的时候 根据当前状态执行成功或者失败
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === 'fulfilled') {
                setTimeout(() => { //这里之所以异步是因为必须保证resolvePromise(promise2, x, resolve, reject)时Promise2创建完成
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            if (this.status === 'rejected') {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
            if (this.status === 'pending') {
                this.onFulfilledCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })

                })
                this.onRejectedCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
            }
        })
        return promise2
    }

}

基本面试5-10分钟代码写到这里,都能给满分通过,剩下的就是4个打补丁的地方了

4.打补丁

4.1 补丁点

实际上是A+规范测试用例的补丁,我按重要程度往下排,前面的必须做到能写出来(面试可以不写),后面的知道即可

  1. then的默认参数配置
  2. x可能是个Promise,它的返回值还可能是个Pormise,这个Promised的返回值还可能是个Promise…
  3. 调用Promise的resolve方法,如果参数是个promise怎么办 (这个不在A+规范里,但是新版promise实现了)
  4. 别人实现的可能不规范,我们的resolvePromise需要加一点限制,改变了状态就不能再变了 (这个在A+规范测试用例里,但是我感觉意义不大)

4.1.1 默认参数

Promise.resolve(1).then().then().then().then((val)=>{
    console.log(val)        //1
})
//失败也是类似的传递

可以默认传递一个回调函数

then(onFufilled,onRejected){
    onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
    ...
}

4.1.2 x中promise嵌套

这个也不难,递归调用resolvePromise去解析

let resolvePromise = (promise2,x,resolve,reject) => {
    ...
    then = x.then
    /*这个是之前的核心代码 then.call(x,resolve,reject)  
    *实际等同于 then.call(x,(y)=>{
    *             resolve(y)   这个y是x作为promise的返回值,现在这个y可能是个promise所以再递归调用resolvePromise去解析
    *          },reject)  
    */
   改成这样:
   then.call(x,(y)=>{
       resolvePromise((promise2,y,resolve,reject)  
   },reject)
    ...
}

4.1.3 resolve中是promise

constructor(executor){
       ...
        let resolve = (value) =>{ // 如果resolve的值时一个promise
            if(value instanceof Promise){
                // 我就让这个promise执行,把成功的结果再次判断
                return value.then(resolve,reject) //参数会自动传递到resolve里面去
            }

        }
        ...

5.添加方法

Promise比较重要的方法一共有五个方法

5.1 Promise.resovle

把一个对象包装成Promise对象,特别注意状态不一定是成功的
各种注意事项请看阮一峰es6-promise
直接记忆不好记忆,但是结合源码很简单,理所当然

static resolve(value){
        return new Promise((resolve,reject)=>{
            resolve(value);
        })
    }

5.2 Promise.reject

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

static reject(err){
       return new Promise((resolve,reject)=>{
           reject(err);
       })
   }

5.3 PromiseInstance.prototype.finally

这个是实例方法,其他几个都是类方法
无论成功还是失败都会调用,所以可定返回的也是一个Promimse,成功失败都会调用传入的回调,
finally不接受值,返回的Promise的状态受前一个promise状态的影响
finally如果在中间同时回调返回一个promise则会等待promise

Promise.resolve(1).finally( 
    (a)=>{
        return new Promise((resolve)=>{
            setTimeout(function () {
                       resolve(2)
                    },3000)
                })

    }
).then((data)=>{
    console.log(data)
})

等待3秒后打印1

finally实现

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

5.4 Promise.race Promise.all

race和all一个是谁先调用谁执行后面then中的回调,一个是全部调用才执行后面then中的回调
他们都需要对参数中传入的数组进行遍历

all的实现需要借助计数器,这也是实现异步任务通知的一种方法
直接完成或者异步完成都会使计数器加1 当计数器和数组长度相等时就是all方法完成的时候,然后把结果数组传到下一个回调

race的实现就是,遍历数组中元素current,都去改变返回promise的值,谁先改变就取谁的值传到会带到函数里面

return promose((resolve,reject)=>{
if(isPromise(current)){
                current.then(resolve,reject)
            }else{
                resolve(current)
            }
})

具体实现见6

6.完整实现

let resolvePromise = (promise2,x,resolve,reject) => {
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 如果调用了失败 就不能再调用成功 调用成功也不能再调用失败
    let called;
    if(typeof x ==='function'  || (typeof x === 'object' && x!== null) ){
        try{
            let then = x.then; // Object,dedefineProperty
            if(typeof then === 'function'){
                then.call(x,(y)=>{   // x.then(y=>,err=>)
                    if(called) return;
                    called = true
                    // y有可能解析出来的还是一个promise
                    // 在去调用resolvePromise方法 递归解析的过程
                    // resolve(y)
                    resolvePromise(promise2,y,resolve,reject); // 总有y是普通值的时候
                },e=>{
                    if(called) return;
                    called = true
                    reject(e);
                })
            }else{ 
                 if(called) return;
                 called = true
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true
            reject(e);
        }
    }else{
         if(called) return;
        called = true
        resolve(x);  // '123'  123
    }
}
class Promise{
    constructor(executor){
        this.value = undefined;
        this.reason = undefined;
        this.status = 'pending';
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];
        let resolve = (value) =>{ // 如果resolve的值时一个promise
            // if(typeof value === 'function' || (typeof value == 'object'&&value !== null)){
            //     if(typeof value.then == 'function'){
            //         return value.then(resolve,reject)
            //     }
            // }
            if(value instanceof Promise){
                // 我就让这个promise执行,把成功的结果再次判断
                return value.then(resolve,reject) //参数会自动传递到resolve里面去
            }
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value;
                this.onResolvedCallbacks.forEach(fn=>fn());
            }
        }
        let reject = (reason) =>{
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn=>fn());
            }
        }
        try{
            executor(resolve,reject);
        }catch(e){
            console.log(e)
            reject(e);
        }
    }
    then(onFufilled,onRejected){
        // 可选参数的配置
        onFufilled = typeof onFufilled === 'function'?onFufilled:value=>value;
        onRejected = typeof onRejected === 'function'?onRejected:err=>{throw err}
        let promise2 = new Promise((resolve,reject)=>{
            if(this.status === 'fulfilled'){
                setTimeout(()=>{ // 为了保证promise2 已经产生了
                    try{
                        let x = onFufilled(this.value);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        console.log(e);
                        reject(e);
                    }
                })
            }
            if(this.status === 'rejected'){
                setTimeout(() => {
                    try{
                        let x= onRejected(this.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
            if(this.status === 'pending'){
                this.onResolvedCallbacks.push(()=>{
                    setTimeout(() => {
                        try{
                            let x = onFufilled(this.value);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    })
                });
                this.onRejectedCallbacks.push(()=>{
                    setTimeout(() => {
                        try{
                            let x= onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject);
                        }catch(e){
                            reject(e);
                        }
                    });
                });
            }
        })
        return promise2
    }
    finally(callback){
        let P = this.constructor;
        return this.then(
            value  => P.resolve(callback()).then(() => value),
            reason => P.resolve(callback()).then(() => { throw reason })
          );
    }
    catch(errCallback){ // catch是then的一个别名而已
        return this.then(null,errCallback)
    }
    static resolve(value){
        return new Promise((resolve,reject)=>{
            resolve(value);
        })
    }
    static reject(err){
        return new Promise((resolve,reject)=>{
            reject(err);
        })
    }

    static race(values){
        return new Promise((resolve,reject)=>{
        for(let i = 0 ; i<values.length;i++){
            let current = values[i];
            if(isPromise(current)){
                current.then(resolve,reject)
            }else{
                resolve(current)
            }
        }
    })
    }
    static all(values){
        return new Promise((resolve,reject)=>{
                let arr = []; // 最终的结果
                let i = 0;
                function processData(key,val) {
                    arr[key] = val; 
                    if(++i == values.length){
                        resolve(arr);
                    }
                }
                for(let i = 0 ; i<values.length;i++){
                    let current = values[i];
                    if(isPromise(current)){
                        current.then(y=>{
                            processData(i,y);
                        },reject)
                    }else{
                        processData(i,current);
                    }
                }
            })

    }
}
Promise.deferred = () => { // 测试方法
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd; // 可以检测这个对象上的promise属性 resolve方法 reject方法
}
module.exports = Promise;

// 全局安装 只能在命令中使用  sudo npm install promises-aplus-tests -g
// promises-aplus-tests promise.js
// 本地安装 可以在命令下 和 我们的代码中使用

7.面试题

7.1 请写出下面代码运行结果

Promise.reject(1).then().finally( 
    (a)=>{
        console.log('a:'a) //undefined
        setTimeout(function () {
            console.log(2)
        },3000)
    }
).then((data)=>{
    console.log(3)
    console.log(data)
},(e)=>{
    console.log('error'+e) //打印error1
})

// 

答案:a:undefined error1 过两秒 2

7.2 promise构造器是同步还是异步,then方法呢

来源:微医
答案:同步,异步 源码里面写的很清楚

7.3 模拟实现一个Promise.finally

答案:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

7.4 介绍一下Promose.all的使用,原理及错误处理

使用:需要同时获取多个东西后再执行回调
原理:返回一个Promise: p 遍历参数数组,若不是promise,直接加入到结果数组arr中 计数器++
如果是Promise,等Promise执行完再讲结果加到加过数组 计数器++
计数器===数组长度时证明全部完成,p.resolve(结果数组arr)
错误处理: p.reject(e)

7.5 设计并实现Promise.race

答案:

Promise._race = promises => new Promise((resolve, reject) => {
    promises.forEach(promise => {
        promise.then(resolve, reject)
    })
})

8.总结

总结了Promise的实现,以及面试常见考点,相信如果全部理解了,面试再问promise肯定可以加分不少。由于技术有限,如果阅读中发现有什么错误,请在留言指出。

小编开了个逐点突破系列,一篇文章来讲一个知识点,学习要系统,知识点也需要归纳总结,文章还会包括常见的相关面试题。当然这个系列文章篇幅会比较长,大家可以收藏慢慢看,有什么建议或可以优化的也欢迎大家提出来,逐点突破系列还请大家多多支持啦!

9.最后

文章中出现的面试题还没有看过瘾的可以【点击这里】免费获取完整版前端面试题解析PDF哦!


如果你觉得本文对你有很大的帮助,喜欢这个系列,请评论点赞转发来告诉我哦!你们的支持是我最大动力!


转载:https://blog.csdn.net/weixin_52546522/article/details/115680287
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场