1. 90前端首页
  2. 前端资讯

前端面试必备——异步(Promise)

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里面的回调执行?只要调用创建promise2时的resolve方法就行了
第二个标胶绕的地方就是参数是什么?我们看特性3.1,参数是什么要根据第一个then中回调的返回值来判断,返回值如果是正常值,如果是Piomise,如果报错处理都不一样,所以我们封装一个resolvePromise的方法来处理

需要改变的核心代码如下
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肯定可以加分不少。由于技术有限,如果阅读中发现有什么错误,请在留言指出
如果你觉得本文对你有很大的帮助,求点赞,求收藏,求打赏,你们的支持是作者写作的最大动力!

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000022075104

展开阅读全文

发表评论

登录后才能评论