Promise,你所不知道的流程及源码实现

358 阅读7分钟

异步的产生和背景

最早的ajax异步回调,在一个异步请求中写入一个callback函数可以实现早期易用的异步调用,同时也陷入了许多的弊端之中,多层嵌套不能一个中断或者报错。、 万能的coder大神们 衍生出了promise。解决了callback hell的困惑和问题。同时改进发展到generator+co更好的方案。如今在非刚需node环境下也有了一套不需要安装多余包即可使用的async+await。

源码解读

//正常promise用法
let promise = New Promise(function(resolve,rejevt){
    //这里是要执行的函数   任何一个函数都有返回正确和错误的流程,这里也一样resolve代表正常返回,reject表示错误返回
})

promise.then(function(res){
    //正常正确的逻辑
},function(err)}{
    //返回异常的信息和结果
})

>>>>>>>>>>>>>>>>>>>>>>>=====分割线   下面开始解读
//promise.js 规范文本  按照https://promisesaplus.com/规范来解读

function Promise(executor){  //executor是一个执行函数,在实例化Promise的时候就开始执行 == 上面Promise的执行参数
    let self = this;
    self.status = 'pending';//任何http请求在没有收到返回信息的时候 状态都是pengding,这里也用来记录改变的状态
    self.value = undefined;//定义成功的数据
    self.reason = undefined;//定义失败的原因

    //如promise用法,这里需要传入2个参数
    //网络请求不可能一直请求,总有一个状态来表示   根据A+规范2.1 Promise States  在没有结果之前 状态值都是可以更改的
     
    function resolve(value){
        //执行正常的操作   传入成功返回值,并修改状态resolved 防止多次调用修改状态值
        if(self.status === 'pending'){
            self.value = value;
            self.status = 'resolved';
        }
    }
    function reject(reason){
        //执行异常的处理  传入异常原因,并修改状态为rejected 防止多次调用修改状态值
        if(self.status === 'pending'){
            self.value = reason;
            self.status = 'rejected';
        }
    }    
    
    executor(resolve,reject)
    
}
//在实例化Promise 之后 可以链式调用then方法。因此then是在Promise的原型连上    根据A+规范2.2 The then Method

Promise.then = function(onFulfilled, onRejected){
    //这里使用  第一个函数为  成功状态下的回调,  第二个函数为失败状态下的回调。怎么和成功失败想联系,需要记得构造函数中的状态值 status  resolved/成功 rejected/失败
    let self = this;
    if(self.status === 'resolved'){
        onFulfilled(self.value)
    }
    if(self.status === 'rejected'){
        onRejected(self.reason)
    }
}

//最终不要忘记暴露这个模块  如果需要使用import 请看

module.exports = Promise

目前这里完全是同步执行的代码

当然在实际应用过程中都是异步的操作,不可能在实例化的时候就直接有结果传给then吧,下面我们来改造一下函数。

    //定义2个数组,来存放回调事件,promise.then(suc1,err1).then(suc2,err2),每一次then的回调都不同
    //在创建promise函数的地方,给每个成功和失败时间存到函数里面。
    
    function Promise(executor) { //executor是一个执行函数,同步执行
        .....
        self.reason = undefined; //默认失败的原因

        self.onResolvedCallBacks = [];//成功的回调数组;
        self.onRejectedCallBacks = [];//失败的回调数组;
    }
    
    Promise.prototype.then = function(resolve,reject){
        ......
        if(self.status === 'pending'){
            //在此处以队列的形式存放所有的事件
            self.onResolvedCallBacks.push(resolve);
            self.onRejectedCallBacks.push(reject);
        }
    }
    
    //如何在成功后的回调里面执行这些函数,回到状态改变的函数执行时候
    function Promise(executor) { //executor是一个执行函数,同步执行
        ...
        if(self.status === 'resolved'){
            self.status = 'resolved';
            self.value = value;
            //在这里把所有成功的函数都通过循环执行一边,全部是顺序同步执行
            self.onResolvedCallBacks.forEach(function(fn){
                fn();
            })
        }
         if(self.status === 'rejected'){
            self.status = 'rejected';
            self.reason = reason;
            //在这里把所有失败的函数都通过循环执行一边,全部是顺序同步执行
            self.onRejectedCallBacks.forEach(function(fn){
                fn();
            })
        }       
    }
    //至此就完成了  异步的操作, 有没有想起类似的一种设计模式,的确很像
    
    
    
    //同时再来解释一下promise的链式调用。以及传空值的值穿透操作。
    
    promise.then(cb,cb).then(cb.cb),//这里有一个误区,并不类似于jq的链式操作返回this  
    
    let promise = new Promise(function(resolve,reject){
        resolve('suc');
    });
    //根据上面直接执行了 resolve成功的函数,then会触发  第一个成功的函数,,返回data,
    let p1 = promise.then(function(data){
        console.log(data)  
    },function(err){
        console.log(err)
    }
    //但是  当  如上promise第一个函数返回的是   let p2 = promise.then(function(data){
        throw new Error('error')
    },function(err){
        console.log(err)
    })  //此时,p1.then  和p2.then  已经分别指向了成功和失败,因此这一点的this指向已经不同。所以这一点不能通过this来链式调用,靠的其实就是promise本身来链式调用的,在每次resolved,rejecred,pending其实都已经重新实例化了一个promise。因此可以一直来then。可参考下列异常2理解返回的promise
    
    promise.then().then(),详解
    
    //只是多做一层判断。如果曾在则使用自身所带的,不存在原值reutrn。。是不是很简单
    Promise.prototype.then = function (onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function'?onFulfilled:function(res){
            return res;
        }
        onRejected = typeof onRejected === 'function'?onRejected:function(err){
            throw err
        }
        ...
    }

小结:对于每个内部构造中的onFulfilled和onRejected按照promise的规定必须包裹一层setTimeout。具体原因需要参考事件环的执行顺序。不多累述。简单了解可看下面代码以及注释。

业务完成并不能代表这个代码符合大多数人的需求,还需要许多的磨合已经使用才能成为一个优秀的库。对此promise对于错误捕获信息的处理也是做到了极致。不得不服大佬对代码细腻程度的把控相当严谨。 以下对于有更高追求的码哥可以深入了解一下。

异常1: promise (function(resolve,reject){ throw new Error('error') }); 是不是直接傻眼了,对此需要在executor这里做一层try {executor(resolve, reject)} catch(e){ reject(e)} 捕获异常错误后直接返回错误信息。

异常2:then之后继续return promise promise.then(function(data){ return new Promise(function(resolve,reject){ ... }) },function(err){ return new Promise(function(resolve,reject){ ... })
})

//不难理解,这里每次都会重新定义一个新的promise来进行保存当前操作已经下一步的操作。。setTimeout暂时可以不理解,需要深入理解请参考promisezAplus中的规范标准。

Promise.prototype.then = function (onFulfilled, onRejected) { ... let promise2;//链式调用定义的新promise //成功和失败都可能会返回一个新的promise if (self.status === 'resolved') { promise2 = new Promise(function(resolve,reject){ setTimeout(function(){ onFulfilled(self.value);} ) })

}
if (self.status === 'rejected') {
    promise2 = new Promise(function(resolve,reject){
        setTimeout(function(){
            onRejected(self.reason);
        })
    })
}
if (self.status === 'pending') {
    promise2 = new Promise(function(resolve,reject){
        self.onResolvedCallBacks.push(function () {
            onFulfilled(self.value);
        })
        self.onRejectedCallBacks.push(function () {
            onRejected(self.value);
        })
    })

}

return promise2;

}

//懵逼异常3:返回自身promise以及无限嵌套promise,并且与他们promise库兼容

function resolvePromise(p2,lastResult,resolve,reject){

//lastResult,有可能是别人返回的promise或者是一个单纯的返回值
//p2 和 lastResult 如果相同,这里应该是不合法的
if(p2 === lastResult){
    //这里应该报一个错误类型
    return reject(new TypeError('循环引用'));
}
//判断lastResult 是不是一个promise,promise应该是一个对象.
let called; // 表示是否调用过成功或者失败
if(lastResult!=null && (typeof lastResult === 'object' || typeof lastResult === 'function')){
    //可能是个promise  也可能是个{}, 是不是个promise  可以用对象中是否有then方法,如果有then就认为是一个promise
    try {  //else 的可能性{then:1}
        let then = lastResult.then;
        //其他的所有形况认为是promise
        if(typeof then === 'function'){
            //成功
            then.call(lastResult,function(y){
                if(called)return 
                called = true;
                //y可能还是一个promise,再去解析  直到返回的是一个普通值
                resolvePromise(p2,y,resolve,reject)
            },
            //失败
            function(err){
                if(called)return 
                called = true;
                reject(err);

            });
        }else{
            resolve(lastResult);
        }
    } catch (e) {
        if (called) return
        called = true;
        reject(e);
    }
    
}else{
    //返回一个普通值  则直接返回成功态
    resolve(lastResult);//表示成功了
}

}

测试 promise是否符合规范。

npm install -g promises-aplus-tests

安装成功后

找到你promise的文件目录打开命令窗口

promises-aplus-tests ./Promise.js

查看是否测试通过。

扩展一些内容

高阶函数

你有多少函数是链式调用的,你又有多少函数可以作为参数或者函数作为返回值

判断数据类型 isType
//正常的简单的判断函数吧
function isType(type,content){
    return Object.prototype.toString.call(content) === `[object ${type}`
}

//高级一点的  
//这里可以批量生成函数
function isType(type){ //偏函数
    return function(content){
        return Object.prototype.toString.call(content) === `[object ${type}`
    }
}
let isString = isType('String');
let isArray = isType('Array');

console.log(isArray('hello'));//false


// 2 预置函数  (在执行函数多少次后执行回调)   在确定外界大的状态或者条件后去执行回调

function after(times,callBack){
    return function(){
        if(--times === 0 ){
            callBack();
        }
    }
}

let eat = after(3,function(){
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/4/8/162a1942427a8084~tplv-t2oaga2asx-image.image)
    console.log('不吃了');
})
eat();//没打印
eat();//没打印
eat();//打印  '不吃了';


这里与promise的执行算是一点同步的见解。
大致是这样的流程

异步读取文件,方法大同小异与promise.all

let fs = require('fs');

let a = [];

function outPut(length,callBack){
    return function(data){
        a.push(data);
        if(--length == 0 ){
            callBack(arr);
        }
    }
}

let out = after(3,function(arr){
    console.log(arr);
})

fs.readFile('文件1路径','utf8',function(err,res){
    outPut(res);//存取其中一条数据
})
fs.readFile('文件2路径','utf8',function(err,res){
    outPut(res);//存取另一条数据
})