最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【逐点突破系列】前端面试必备——异步(Promise)

    正文概述 掘金(李不要熬夜)   2021-04-20   562

    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哦!

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

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

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


    起源地下载网 » 【逐点突破系列】前端面试必备——异步(Promise)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元