掘金上一篇Promise源码重现的转载(内含自我理解和注释)

转载:这一次,彻底弄懂 Promise 原理 - 掘金 (juejin.cn)

图解 Promise 实现原理(一)—— 基础实现 - 知乎 (zhihu.com)

Promise

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)。

基本过程

1.初始化 Promise 状态(pending)

2.立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理

3.执行 then(…) 注册回调处理数组(then 方法可被同一个 promise 调用多次)

4.Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。

链式调用:

关于new关键字;

其伪代码实现为:

new Person('james', 18)  = {
  var obj = {};//创建空对象
  obj.__proto__ = Person.prototype;//绑定对象原型指向其构造函数
  var res = Person.call(obj, 'james', 18);//指向构造函数 
  return typeof res === 'object' ? res : obj;
}

再来分析一段代码的执行顺序:

 new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
            resolve({ test: 2 })
            reject({ test: 2 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
    },(data1)=>{
        console.log('result2',data1)
    }).then((data) => {
        console.log('result3', data)
    })
    //result1 { test: 1 }
    //result3 undefined
//new时会先执行Promise内部的构造函数,目前对于我们是隐式的,传参进去的fn会在构造函数中被调用,并且传递两个能改变Promise状态的函数,这两个函数可以在fn的作用域内向外传递,使得Promise的状态能被异步获取
//在new执行完成后,会调用then方法注册成功或失败后的事件操作
//在resolve,rejected执行以后,Promise内部状态改变,且调用其注册在then中的任务队列

链式调用:then方法的返回值依旧是Promise对象,且每次 then 返回了新的 Promise。then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的。

源码手写:

    function Promise(fn){ //构造函数 内部为传入的fn
        let state = 'pending';//初始化状态
        let value = null;//携带值
        const callbacks = [];//回调队列
        this.then = function (onFulfilled){//模拟编写then方法 此处先只有一个传入的回调 然后返回值也需要是一个Promise对象;
            return new Promise((resolve, reject)=>{
                handle({ 
                    onFulfilled, //预期的回调方法
                    resolve//新Promise的resolve方法
                })
            })
        }
        function handle(callback){
            if(state === 'pending'){//如果是pending状态先将传入的回调加入到callbacks回调队列中
                callbacks.push(callback)
                return;
            }
            if(state === 'fulfilled'){//如果状态改变
                if(!callback.onFulfilled){//没有传入预期回调
                    callback.resolve(value)//将新promise的resolve调用 值为上一个promise的值
                    return;
                }
                const ret = callback.onFulfilled(value) //处理预期回调
                callback.resolve(ret) //处理下一个 promise 的resolve
            }
        }
        function resolve(newValue){//关于resolve方法
            const fn = ()=>{
                if(state !== 'pending')return//只能由它转换
                state = 'fulfilled';
                value = newValue//调整状态和值
                handelCb()//调用该方法
            }
            setTimeout(fn,0) //基于 PromiseA+ 规范
            //将其致于宏任务中在本轮微任务执行后进行,以便后续then方法回调函数的组册
        }
        
        function handelCb(){
            while(callbacks.length) {
                const fulfiledFn = callbacks.shift();
                handle(fulfiledFn);//排空执行注册的回调
            };
        }
        
        fn(resolve)//fn调用;
    }

关于宏微任务的执行:

一、同步——>异步——>微任务——>宏任务。JS是单线程,碰见同步执行同步直到执行完毕,遇到异步放到执行队列中去,异步包括宏任务和微任务,在异步中微任务是优于宏任务执行的。

二、

了解Promise内部实现后再来分析上一段代码的执行

1.执行同步代码new关键字 new内部初始化了几个函数方法并且最后调用了我们传入的fn 传入了提供给我们的resolve函数。

2.这里还是属于同步代码区段,进入fn的函数执行,执行我们写的方法,调用setto初始化了一个宏任务。

3.退出fn退出promise构造函数,返回promise对象,调用then方法,then方法返回一个新的promiseB对象,且在其中调用了handle方法,属于同步代码区段。

4.handle调用,传入了我们在then中组册的回调和自己的resolve方法,这里又将执行一个构造函数和fn然后进入handle。

5.handle方法判断当前promise状态,用来组册回调事件到对象的回调队列上。然后退出方法,目前仍然是同步。(后序的.then都是一系列同步new Promise执行完成后组册给新Promise的回调方法)

6.当外界调用resolve方法时,会传入一个新的value,组册一个函数并设置其为宏任务;

7.在同步一轮以及微任务执行完后,resolve注册的函数执行,更换了原Promise的状态和值,此时调用handelCb方法,排空callbacks队列,再调用handle方法。

8.这次handle方法进入另一逻辑分支,执行组册的成功回调(或失败),并且resolve下一个promise(接收成功回调的返回值做newvalue)的resolve。

这个模型简单易懂,这里最关键的点就是在 then 中新创建的 Promise,它的状态变为 fulfilled 的节点是在上一个 Promise的回调执行完毕的时候。也就是说当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去…链式调用的效应就出来了。

作者:winty
链接:https://juejin.cn/post/6844904063570542599
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


用上面的Promise的resolve方法,执行下面方法后

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ test: 1 })
  }, 1000)
}).then((data) => {
  console.log('result1', data)
  //dosomething
  return test()//执行到此其返回值会被传入成新promise的resolve value 但是这里没有区别返回值的类型
}).then((data) => {
  console.log('result2', data)
})

function test(id) {
  return new Promise(((resolve) => {
    setTimeout(() => {
      resolve({ test: 2 })
    }, 5000)
  }))
}
//基于第一个 Promise 模型,执行后的输出
//result1 { test: 1 }
//result2 Promise {then: ƒ}

所以需要拓展我们的resolve函数:

function resolve(newValue){
    const fn = ()=>{
        if(state !== 'pending')return
        if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
            const {then} = newValue
            if(typeof then === 'function'){
                // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
                //相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
                //这样依赖,如果传入promise,会等其执行完成后调用then方法并把上个promise的resolve调用;
                then.call(newValue,resolve)
                return//特殊情况处理
            }
        }
        state = 'fulfilled';//普通行为处理
        value = newValue
        handelCb()
    }

    setTimeout(fn,0)
}

再来写reject方法,并且补全之前代码里的判断逻辑:

function Promise(fn){ 
    let state = 'pending';
    let value = null;
    const callbacks = [];
    this.then = function (onFulfilled,onRejected){
            return new Promise((resolve, reject)=>{
                handle({
                    onFulfilled, 
                    onRejected,//新增一种组册回调
                    resolve, 
                    reject
                })
            })
        }

        function handle(callback){
            if(state === 'pending'){
                callbacks.push(callback)
                return;
            }
            
            const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;//动态选择回调,因为只能有一类回调被执行
            const next = state === 'fulfilled'? callback.resolve:callback.reject;//同上
            if(!cb){//判断执行哪一类回调
                next(value)
                return;
            }
            const ret = cb(value)
            next(ret)
        }
    
        function resolve(newValue){***}
        
        function reject(error){
            const fn = ()=>{
                if(state !== 'pending')return
                if(error && (typeof error === 'object' || typeof error === 'function')){//判断是否返回值为promise
                    const {then} = error
                    if(typeof then === 'function'){
                     then.call(error,resolve,reject)
                        return
                    }
                }
                state = 'rejected';
                value = error
                handelCb()
            }
            setTimeout(fn,0)
        }
        function handelCb(){***}
        fn(resolve, reject)
    }

异常处理代码:

异常通常是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,我们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态即可。

可以通过handle代码改造来实现:

    function handle(callback){
        if(state === 'pending'){
            callbacks.push(callback)
            return;
        }
        const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
        const next = state === 'fulfilled'? callback.resolve:callback.reject;

        if(!cb){
            next(value)
            return;
        }
        try {
            const ret = cb(value)
            next(ret)
        } catch (e) {
            callback.reject(e);
        }  
    }

在实际使用时,我们会习惯组册catch方法来处理错误:

    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
        //dosomething
        return test()
    }).catch((ex) => {
        console.log('error', ex)
    })

catch方法可以通过then的错误回调来进行处理,通过链式调用传递下来的reject错误态promise。

        this.then = function (onFulfilled,onRejected){
            return new Promise((resolve, reject)=>{
                handle({
                    onFulfilled, 
                    onRejected,
                    resolve, 
                    reject
                })
            })
        }
        this.catch = function (onError){
            this.then(null,onError)
        }

方法拓展

Finally方法:在实际应用的时候,我们很容易会碰到这样的场景,不管Promise最后的状态如何,都要执行一些最后的操作。我们把这些操作放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。所以我们可以这样写 finally 的逻辑:

function Promise(fn){ 
    ...
    this.catch = function (onError){
        this.then(null,onError)
    }
    this.finally = function (onDone){
        this.then(onDone,onDone)
    }
    ...//无论结果值的调用
}

resolve 方法和 reject 方法

这些情况下,Promise.resolve 的入参可能有以下几种情况:

  • 无参数 [直接返回一个resolved状态的 Promise 对象]
  • 普通数据对象 [直接返回一个resolved状态的 Promise 对象]
  • 一个Promise实例 [直接返回当前实例]
  • 一个thenable对象(thenable对象指的是具有then方法的对象) [转为 Promise 对象,并立即执行thenable对象的then方法。]
    function Promise(fn){ 
        ...
        this.resolve = function (value){
            if (value && value instanceof Promise) {
                return value;//如果是promise
            } else if (value && typeof value === 'object' && typeof value.then === 'function'){//如果有then方法
                let then = value.then;
                return new Promise(resolve => {
                    then(resolve);
                });
            } else if (value) {
                return new Promise(resolve => resolve(value));
            } else {
                return new Promise(resolve => resolve());
            }
        }
        ...
    }

作者:winty
链接:https://juejin.cn/post/6844904063570542599
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

//判断上述四点的不同传入

Promise.all:

入参是一个 Promise 的实例数组,然后注册一个 then 方法,然后是数组中的 Promise 实例的状态都转为 fulfilled 之后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 之后就保存该实例返回的数据,然后将计数减一,当计数器变为 0 时,代表数组中所有 Promise 实例都执行完毕。

function Promise(fn){ 
    ...
    this.all = function (arr){
        var args = Array.prototype.slice.call(arr);//复制一份新数组
        return new Promise(function(resolve, reject) {
            if(args.length === 0) return resolve([]);//长度为0返回空数组
            var remaining = args.length;
            function res(i, val) {
                try {
                    if(val && (typeof val === 'object' || typeof val === 'function')) {//判断传入的对象有then方法
                        var then = val.then;
                        if(typeof then === 'function') {
                            then.call(val, function(val) {//then方法调用,并且传入该匿名回调做为成功回调
                                res(i, val);//成功回调完成后调用该函数
                            }, reject);
                            return;//如果是
                        }
                    }
                    args[i] = val;//将args[i]上的promise转换为对应的值
                    if(--remaining === 0) {
                        resolve(args);
                    }//一旦length归0处理完成
                } catch(ex) {//捕获回调抛出的异常
                    reject(ex);
                }
            }
            for(var i = 0; i < args.length; i++) {
                res(i, args[i]);
            }
        });
    }
    ...
}

总结

1.其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变之后。

2.而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中。

3.new和宏微任务的判断和执行也十分重要

补充说明 虽然 then 普遍认为是微任务。但是浏览器没办法模拟微任务,目前要么用 setImmediate
,这个也是宏任务,且不兼容的情况下还是用 setTimeout 打底的。还有,promise 的 polyfill
(es6-promise) 里用的也是 setTimeout。因此这里就直接用 setTimeout,以宏任务来代替微任务了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值