nodejs promise介绍

promise 概念

ES6 原生支持Promise
Promise是一个构造函数,包含all,reject,resolve等方法。一个Promise用来传递异步操作的消息,它代表了未来才会知道结果的事件。
本质上是一种异步编程的抽象,是返回值或者抛出exception的代理对象。它有一个then方法,任何人都可以访问then来消费这个返回值或exception的。

三种状态

promise只有三种状态,并且状态转移只能是pending -> fulfiled 或者pending -> rejected
pending: 待定状态,Pormise对象刚被初始化的状态
fulfilled: 完成状态,承诺被完成的状态
rejected: 拒绝状态,承诺完成失败的状态 relosve和reject: resolve() 和 reject()是 Promise的两个闭包函数。

通常,读取一个文本,我们会这么写。

"use strict"
const fs = require('fs');  
fs.readFile('test.txt', (err, data) => {  
    if(err){
        console.error(err);
        return;
    }
    console.log(data);
});

接下来我们来封装类似promise风格的函数,如下:

"use strict"
const fs = require('fs');  
let proReadFile = (filename) => {  
    return new Promise( (resolve, reject) => { 
        fs.readFile(filename, (err, data) => {
            if(err){
                reject(err);
                return;
            }
            resolve(data);
        });
    });
};

proReadFile('text.txt').then(console.log).catch(console.error);  

这里把fs.readFile封装成一个promise如果读入正常就利用then把数据交给console.log函数消费,如果失败就利用catch把err交给console.error消费

分析代码

源码 一个基本的MyPromise

//函数构造器
function MyPromise(exe) {

    //三种状态,默认"pending"
    this.status = "pending";
    //如果状态是rejected, reason 的值非空,带给消费者消费
    this.reason = null;
    //如果状态是fulfilled, result 的值非空,带给消费者消费
    this.result = null;

    //resolve回调函数数组,当exe执行器执行完,触发该函数数组,而后修改状态为fulfilled    
    this.resolveArr = [];
    //reject回调函数数组,当exe执行器执行完,触发该函数数组,而后修改状态为rejected
    this.rejectArr  = [];

    //保证各个函数里面的变量是该对象的属性
    let self = this;

    //promise留给外部的闭包函数,依次触发resolveArr里面的函数
    let resolve = () => {
        if("pending" != self.status) {
            return ;
        }
        //setTimeout异步的目的,是为了让then的方法先执行,将消费函数先注册上
        //否则resolve执行完,then里面的函数并不会执行
        setTimeout( () => {
            let fn = null;
            let result = self.result;
            while(fn = self.resolveArr.shift()) {
                result = fn(result);
            }
            self.result = result;
            self.status = "fulfilled";
        });
    }
    //同理如上
    let reject  = () => {
        if("pending" != self.status) {
            return ;
        }
        setTimeout( ()=> {
            let fn = null;
            let reason = self.reason;
            while(fn = self.resolveArr.shift()) {
               reason = fn(reason);
            }

            self.reason = reason;
            self.status = "rejected";
        });
    }
    exe(resolve, reject);
}

再来看看then链式结构

MyPromise.prototype.then = (onFulfilled, onRejeceted) => {  
    let self = this;
    //最终返回promise对象,保证thenable
    return new MyPromise( (resolve, reject) => {
        //当promise.status 为"resolved", 成功执行下一个链式
        function cb(result) {
            let ret = ("function" == typeof onFulfilled && onFulfilled(result)) || result;
            if(ret["then"]) {
                ret.then( (result)=>{
                    resolve(result);
                }, (reason)=>{
                    reject(reason);
                });
            } else {
                resolve(ret);
            }
        }

        //当promise.status 为"rejected", reject就把reason传递给执行下一个链式
        function errcb(reason) {
            let ret = ("function" == typeof onRejeceted && onRejeceted(reason)) || reason;
            reject(reason);
        }

        if("pending" == self.status) {
            self.resolveArr.push(cb);
            self.rejectArr.push(errcb);
        } else if ("rejected" == self.status) {
            errcb(self.reason);
        } else if ("resolved" == self.status) {
            cb(self.result);
        }
    });
}

现在看看resolve函数,reject同理:

  • MyPromise的构造函数,new出新的promise对象P1,会立即执行使用者传递的执行器exe,当exe运行成功,便会触发resolve函数,并把数据交给resolve函数。但由于resolve里面的setTimouet不会立即触发resolveArr里面函数。这时候resolveArr里面还是空的(假设exe里面从执行到触发resolve都是同步执行的)。
  • resolveArr里面的函数是怎么来的,在then里面构造promise对象P2时,从then里面获取的: self.resolveArr.push(cb)。这时候,只是把cb不停地塞进resolveArr里面,但不会立即执行。
  • 直到P1的resolve里面的异步setTimeout超时,匿名函数执行。不停地弹出resolveArr里面的函数,这里面的函数全是cb, cb的参数是P1的result。
  • 就拿上叙第一例说明,cb和console.log是什么关系。其实简单一点,console.log就是onFulfilled,会被cb函数在其中间部分调用。先不看if(ret["then"]),先看else,就是把onFulfilled里面结果或者说是P2结果赋值给P2的resolve执行,又是promise的核心。

catch 是 then 的语法糖,类似then(null, reject);