29

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

 4 years ago
source link: https://segmentfault.com/a/1190000021040201
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

~

Promise篇

6vqeuiq.png!web

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

其余后面会谈到

正式开始:

乞丐版:

EjmeM3M.png!web

编写逻辑:

1. Promisenew 调用

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

3. Promise 拥有. then 方法

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

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

BB3IZjB.jpg!web

编写思路

状态只能由 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 = []

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

BfUNNnV.jpg!web

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

amQ7vaI.png!web

编写思路

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

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

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

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

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

看下面这段 Node.js 代码:

vM7Z7r2.jpg!web

上面场景,我们读取完 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 可以做到有序执行

Yn6FfiQ.png!web

我们看 bridgePromise 的源码:

EzaMfi6.png!web

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

下面就是流程图:

mEveyyv.jpg!web

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

符合 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 方法:

BRVJjeB.jpg!web

race 方法

JRvmMzv.png!web

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

MZ3Ev2e.png!web

实现 promisify 方法~

MNrui2Z.png!web

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

IfUFFvv.png!web

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

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

1.从零手写一个React

2.从零编写一个webpack

3.从零实现一个websocket

4.从零实现一个vue

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

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

即时通讯

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

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

原生JavaScript


Recommend

  • 33
    • 掘金 juejin.im 5 years ago
    • Cache

    你也可以手写自己的Promise(一)

    我写这篇文章不打算介绍Promise产生的原因以及它解决的问题,我只是想写一篇关于实现自己Promise的文章。如果代码以及逻辑有什么不对的地方,请大家指出来。就这些,开始正题。 前提:我们要知道Promise是基于Promises/A+规范的。其中好多变量和

  • 42

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

  • 36

    前言 [实践系列] 主要是让我们通过实践去加深对一些原理的理解。 [实践系列]前端路由 [实践系列]Babel原理 有兴趣的同学可以关注 [实践系列] 。 求star求follow~ 什么是Promise ? Promise是JS异步编程中的重要概念,异步抽

  • 36

    JS 高级之手写一个Promise,Generator,async和 await【近 1W字】 1.高级 WEB 面试会让你手写一个Promise,Generator 的 PolyFill(一段代码); 2.在写之前我们简单回顾下他们的作用; 3.手写模块见PolyFill. 源码地址

  • 13

    关注  高级前端进阶 ,回复“ 加群 ” 加入我们一起学习,天天进步 作者:晴天酱85223 来源:https://juejin.cn/post/690083445290198631...

  • 2

    手写Promise/Promise.all/Promise.race(手写系列一)饥人谷若愚饥人谷前端,培养有灵魂的前端工程师

  • 8
    • zhuanlan.zhihu.com 3 years ago
    • Cache

    手写Promise周边方法

    手写Promise周边方法夏日Enjoy what you are doing! 在阅读本文之前...

  • 8
    • xieyufei.com 3 years ago
    • Cache

    从零开始手写Promise

      面试的时候经常会问到Promise的使用;有的面试官再深入一点,会继续问是否了解Promise的实现方式,或者有没有阅读过Promise的源码;今天我们就来看一下,Promise在内部是如何实现来链式调用的。 什么是Promise  所谓Promise,简单说就是...

  • 8

    手写 Promise 20 行最简实现(面试必备) 拜拜了,2020!来看看这一年你在社区做了什么~ 你的 2021 又会有哪些关键词?

  • 3

    ts 递归的将一个对象上的所有方法改成返回promise type Promiseify<T> = T extends Promise<infer P> ? T : Promise<T>; type apisPromise<P> = { readonly [K in keyof P]: P[K] extends (...args: any) => any...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK