所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise出现之前都是通过回调函数来实现,回调函数本身没有问题,但是嵌套层级过深,很容易掉进回调地狱

const fs = require('fs');
fs.readFile('1.txt', (err,data) => {
    fs.readFile('2.txt', (err,data) => {
        fs.readFile('3.txt', (err,data) => {
            //可能还有后续代码
        });
    });
});

如果每次读取文件后还要进行逻辑的判断或者异常的处理,那么整个回调函数就会非常复杂且难以维护。Promise的出现正是为了解决这个痛点,我们可以把上面的回调嵌套用Promise改写一下:

const readFile = function(fileName){
    return new Promise((resolve, reject)=>{
        fs.readFile(fileName, (err, data)=>{
            if(err){
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

readFile('1.txt')
    .then(data => {
        return readFile('2.txt');
    }).then(data => {
        return readFile('3.txt');
    }).then(data => {
        //...
    });

Promise规范

promise最早是在commonjs社区提出来的,当时提出了很多规范。比较接受的是promise/A规范。但是promise/A规范比较简单,后来人们在这个基础上,提出了promise/A+规范,也就是实际上的业内推行的规范;es6也是采用的这种规范,但是es6在此规范上还加入了Promise.all、Promise.race、Promise.catch、Promise.resolve、Promise.reject等方法。

我们可以通过脚本来测试我们写的Promise是否符合promise/A+的规范。将我们实现的Promise加入以下代码:

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

然后通过module.exports导出,安装测试的脚本:

npm install -g promises-aplus-tests

在实现Promise的目录执行以下命令:

promises-aplus-tests promise.js

接下来,脚本会对照着promise/A+的规范,对我们的脚本来一条一条地进行测试。

Promise基本结构

我们先回顾一下,我们平时都是怎么使用Promise的:

var p = new Promise(function(resolve, reject){
    console.log('执行')
    setTimeout(function(){
        resolve(2)
    }, 1000)
})
p.then(function(res){
    console.log('suc',res)
},function(err){
    console.log('err',err)
})

首先看出来,Promise是通过构造函数实例化一个对象,然后通过实例对象上的then方法,来处理异步返回的结果。同时,promise/A+规范规定了:

promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;
一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因
    function resolve(value) {}
    function reject(reason) {}
}

Promise.prototype.then = function (onFulfilled, onRejected) {
};

module.exports = Promise;

当我们实例化Promise时,构造函数会马上调用传入的执行函数executor,我们可以试一下:

let p = new Promise((resolve, reject) => {
    console.log('执行了');
});

因此在Promise中构造函数立马执行,同时将resolve函数和reject函数作为参数传入:

function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因
    function resolve(value) {}
    function reject(reason) {}
    executor(resolve, reject)
}

但是executor也会可能存在异常,因此通过try/catch来捕获一下异常情况:

try {
    executor(resolve, reject);
} catch (e) {
    reject(e);
}

不可变

promise/A+规范中规定,当Promise对象已经由等待态(Pending)改变为执行态(Fulfilled)或者拒绝态(Rejected)后,就不能再次更改状态,且终值也不可改变。

 

从零开始手写Promise

 

因此我们在回调函数resolve和reject中判断,只能是pending状态的时候才能更改状态:

function resolve(value) {
    if(_this.state === PENDING){
        _this.state = FULFILLED
        _this.value = value
    }
}
function reject(reason) {
    if(_this.state === PENDING){
        _this.state = REJECTED
        _this.reason = reason
    }
}

我们更改状态的同时,将回调函数中成功的结果或者失败的原因都保存在对应的属性中,方便以后来获取。

then实现

当Promise的状态改变之后,不管成功还是失败,都会触发then回调函数。因此,then的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。

Promise.prototype.then = function (onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if(this.state === REJECTED){
        typeof onRejected === 'function' && onRejected(this.reason)
    }
};

在规范中也说了,onFulfilled和onRejected是可选的,因此我们对两个值进行一下类型的判断:

onFulfilled 和 onRejected 都是可选参数。如果 onFulfilled 不是函数,其必须被忽略。如果 onRejected 不是函数,其必须被忽略

代码写到这里,貌似该有的实现方式都有了,我们来写个demo测试一下:

var myP = new Promise(function(resolve, reject){
    console.log('执行')
    setTimeout(function(){
        reject(3)
    }, 1000)
});

myP.then(function(res){
    console.log(res)
},function(err){
    console.log(err)
});

然鹅,很遗憾,运行起来我们发现只打印了构造函数中的执行,下面的then函数根本都没有执行。我们整理一下代码的运行流畅:

 

从零开始手写Promise

 

当then里面函数运行时,resolve由于是异步执行的,还没有来得及修改state,此时还是PENDING状态;因此我们需要对异步的情况做一下处理。

支持异步

那么如何让我们的Promise来支持异步呢?我们可以参考发布订阅模式,在执行then方法的时候,如果当前还是PENDING状态,就把回调函数寄存到一个数组中,当状态发生改变时,去数组中取出回调函数;因此我们先在Promise中定义一下变量:

function Promise(executor) {
    this.onFulfilled = [];//成功的回调
    this.onRejected = []; //失败的回调
}

这样,当then执行时,如果还是PENDING状态,我们不是马上去执行回调函数,而是将其存储起来:

Promise.prototype.then = function (onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if(this.state === REJECTED){
        typeof onRejected === 'function' && onRejected(this.reason)
    }
    if(this.state === PENDING){
        typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
        typeof onRejected === 'function' && this.onRejected.push(onRejected)
    }
};

存储起来后,当resolve或者reject异步执行的时候就可以来调用了:

function resolve(value) {
    if(_this.state === PENDING){
        _this.state = FULFILLED
        _this.value = value
        _this.onFulfilled.forEach(fn => fn(value))
    }
}
function reject(reason) {
    if(_this.state === PENDING){
        _this.state = REJECTED
        _this.reason = reason
        _this.onRejected.forEach(fn => fn(reason))
    }
}

有童鞋可能会提出疑问了,为什么这边onFulfilled和onRejected要存在数组中,直接用一个变量接收不是也可以么?下面看一个例子:

var p = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve(4)
    }, 0)
})
p.then((res)=>{
    //4 res
    console.log(res, 'res')
})
p.then((res1)=>{
    //4 res1
    console.log(res1, 'res1')
})

我们分别调用了两次then,如果是一个变量的话,最后肯定只会运行后一个then,把之前的覆盖了,如果是数组的话,两个then都能正常运行。

至此,我们运行demo,就能如愿以偿的看到运行结果了;一个四十行左右的简单Promise垫片就此完成了。这里贴一下完整的代码:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因

    this.onFulfilled = [];//成功的回调
    this.onRejected = []; //失败的回调
    function resolve(value) {
        if(_this.state === PENDING){
            _this.state = FULFILLED
            _this.value = value
            _this.onFulfilled.forEach(fn => fn(value))
        }
    }
    function reject(reason) {
        if(_this.state === PENDING){
            _this.state = REJECTED
            _this.reason = reason
            _this.onRejected.forEach(fn => fn(reason))
        }
    }
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
Promise.prototype.then = function (onFulfilled, onRejected) {
    if(this.state === FULFILLED){
        typeof onFulfilled === 'function' && onFulfilled(this.value)
    }
    if(this.state === REJECTED){
        typeof onRejected === 'function' && onRejected(this.reason)
    }
    if(this.state === PENDING){
        typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
        typeof onRejected === 'function' && this.onRejected.push(

相关文章: