53

~

Promise

image.png

本文皆在实现Promise的所有方法,代码均测试可运行,编写于2019年11月17日

GitHub仓库更有自己实现的webpack、mini-react、redux、react-redux、websocket , Electron跨平台桌面端、React-native移动端开源项目等

仓库地址:

https://github.com/JinJieTan

回顾Promise:

new Promise中的构造器函数同步执行

then的回调函数以微任务的形式执行

调用resolve,reject后,状态不能再被改变,传递的值也是

每次.then后返回的还是一个promise

promise可以嵌套

其余后面会谈到

正式开始:

乞丐版:

image.png

编写逻辑:

1.Promisenew 调用

2.每次失败或者成功需要指定回调函数,并且可以传递值

3.Promise拥有.then方法

上面代码有个问题,状态改变应该是异步的,.then应该是微任务形式执行

异步改变状态并且支持三种状态版本:

编写思路

状态只能由Pending改变,而且只能改变一次

异步改变状态,异步的执行.then

支持链式调用

写到这里,需要暂停,捋一捋思路。

万事开头难,其实编写一个Promise是非常简单的事情,看懂上面这两段代码,然后彻底搞清楚这三点,再往下看。

Promise的构造器函数是同步执行

resolve、reject的调用是同步调用,异步执行.例如resolve()是同步调用了resolve这个函数,但是resolve函数内部的代码是异步的。---即异步改变状态,异步执行.then

new Promise((resolve,reject)=>{ 
console.log('这里是同步执行') 
resolve(‘resolve函数内部是异步执行’)
}).then(()=>{
  console.log('这里等resolve函数内部异步执行,状态改变以后再执行')
})

彻底搞懂上面的这个例子和两句话,然后你就可以往下看了,其实下面也都是一些重复或者细节处理的工作

支持.then的链式调用

想支持链式操作,其实很简单,首先存储回调时要改为使用数组


self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = []
 

当然执行回调时,也要改成遍历回调数组执行回调函数

最后,then方法也要改一下,只需要在最后一行加一个return this即可,这其实和jQuery链式操作的原理一致,每次调用完方法都返回自身实例,后面的方法也是实例的方法,所以可以继续执行。

编写思路

1.将所有的成功与失败回调函数存在数组中,遍历执行

2.为了支持链式调用,返回this实例对象即可

3.每次改变状态,清空所有的队列回调

4.目前这种方式只支持同步的回调,下面会加入支持异步


异步链式调用,好像是这里最难的点,但是在应用层里的难点,加一个中间层就能解决,实在不行加两个 ---来自国内不知名码农

看下面这段Node.js代码:

上面场景,我们读取完1.txt后并打印1.txt内容,再去读取2.txt并打印2.txt内容,再去读取3.txt并打印3.txt内容,而读取文件都是异步操作,所以都是返回一个promise

我们上一节实现的promise可以实现执行完异步操作后执行后续回调,但是本节的回调读取文件内容操作并不是同步的,而是异步的,所以当读取完1.txt后,执行它回调onFulfilledCallbacks里面的f1,f2,f3时,异步操作还没有完成,所以我们本想得到这样的输出:

this is 1.txt
this is 2.txt
this is 3.txt

但是实际上却会输出

this is 1.txt
this is 1.txt
this is 1.txt

上面遇到的问题,有点像一个面试题,考闭包的一个循环。看打印输出几,大家应该有印象。


支持异步链式调用

每次.then返回一个新的promise,这个新的promise拥有它独自对应的成功和失败回调(相当于中间层)

同样每次状态改变就清空当前promise对应的回调队列

修改.then方法

.then在链式调用中会被执行多次,这里是本文的重点

思路解析:

首先判断当前Promise的状态,如果状态没有改变,那就全部添加到队列中

调用resolve函数,同样清空队列中所有的任务,不同点在于bridgePromise这个用来桥接的Promise(看成中间层),下面给出例子详解

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    //防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}

这个例子,里面有异步的代码,但是Promise可以做到有序执行

我们看bridgePromise的源码:

代码很简单 就是递归调用,直到返回的不是一个Promise,那么调用resolve清空队列,并且把返回的值存储在self属性上,提供给下一个任务使用。

下面就是流程图:

这里一定要看清楚,本文的重点基本都在这里。


符合Promise A+规范,修改resolvePromise函数即可


function resolvePromise(bridgepromise, x, resolve, reject) {
    //2.3.1规范,避免循环引用
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
    let called = false;
    //这个判断分支其实已经可以删除,用下面那个分支代替,因为promise也是一个thenable对象
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
        // 2.3.3规范,如果 x 为对象或者函数
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            // 是否是thenable对象(具有then方法的对象/函数)
            //2.3.3.1 将 then 赋为 x.then
            let then = x.then;
            if (typeof then === 'function') {
            //2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
            //2.3.3.4 如果 then不是一个函数,则 以x为值fulfill promise。
                resolve(x);
            }
        } catch (e) {
        //2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

实现es6 promise的all,race,resolve,reject方法

all方法:

race 方法

resolve,reject方法~ 快速定义一个成功或者失败状态的Promise

实现promisify方法~

实现catch方法~其实就是不传第一个参数.then方法的语法糖

到这里,一个Promise 并且符合A+规范的所有方法就实现了,网上的实现方式有很多都不一样,本文以一个比较简单明了的方式去实现了它。希望能帮助到大家更清楚的了解Promise

后面会针对下面的内容出专题系列文章~ 欢迎关注本公众号:前端巅峰

1.从零手写一个React

2.从零编写一个webpack

3.从零实现一个websocket

4.从零实现一个vue

5.优雅的让react和vue一起开发

本公众号注重关注技术方向

即时通讯

Electron跨平台桌面端、React-native移动端、Taro小程序

Node.js全栈工程师方向、分布式微服务

原生JavaScript


PeterTan
14.4k 声望30k 粉丝