从零手写逐步实现Promise-A标准的所有方法

13次阅读

共计 4413 个字符,预计需要花费 12 分钟才能阅读完成。

~

Promise

本文皆在实现 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可以嵌套

其余后面会谈到

正式开始:

乞丐版:

编写逻辑:

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

正文完
 0