6

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

 3 years ago
source link: https://blog.csdn.net/weixin_52546522/article/details/115680287
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

通过手写符合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)
    })
})

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK