手写 Promise 符合 Promise/A+规范
异步编程是前端开发者必需的技能,过去管理异步的主要机制都是通过函数回调,然而会出现像“回调地狱”这样的问题。为了更好的管理回调,ES6 增加了一个新的特性 Promise。Promise 是 ES7 中async/await 语法的基础,是 JavaScript 中处理异步的标准形式,现实开发中基本离不开 Promise 了。我们接下来会根据 Promises/A+ 规范自己实现一个 Promise。
完整的代码可以点击我的 github 进行查看,如果你喜欢,欢迎 star,如果发现有问题或者错误,也欢迎提出来。
Promises/A+ 规范
首先我们来看 Promises/A+ 规范的具体内容,Promises/A+ 规范可以查看 Promises/A+,下面是翻译的规范供参考
术语
- promise 是一个有 then 方法的对象或者是函数,行为遵循本规范
- thenable 是一个有then方法的对象或者是函数
- value 是 promise 状态成功时的值,包括 undefined、thenable、promise
- exception 是一个使用 throw 抛出的异常值
- reason 是promise状态失败时的值
要求
2.1 Promise 状态
Promise 必须处于以下三个状态之一: pending, fulfilled 或者 rejected。
2.1.1 如果 promise 在 pending 状态
2.1.1.1 可以变成 fulfilled 或者是 rejected
2.1.2 如果 promise 在 fulfilled 状态
2.1.2.1 不会变成其它状态
2.1.2.2 必须有一个value值
2.1.3 如果 promise 在 rejected 状态
2.1.3.1 不会变成其它状态
2.1.3.2 必须有一个 promise 被 reject 的 reason
2.2 then 方法
promise 必须提供一个 then 方法,来访问最终的结果
promise 的 then 方法接收两个参数
- promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可选参数:
2.2.1.1 onFulfilled 必须是函数类型
2.2.1.2 onRejected 必须是函数类型
2.2.2 如果 onFulfilled 是函数:
2.2.2.1 必须在 promise 变成 fulfilled 时,调用 onFulfilled,参数是 promise 的 value
2.2.2.2 在 promise 的状态不是 fulfilled 之前,不能调用
2.2.2.3 onFulfilled 只能被调用一次
2.2.3 如果 onRejected 是函数:
2.2.3.1 必须在promise变成 rejected 时,调用 onRejected,参数是promise的reason
2.2.3.2 在promise的状态不是 rejected 之前,不能调用
2.2.3.3 onRejected 只能被调用一次
2.2.4 onFulfilled 和 onRejected 应该是微任务
2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
2.2.6 then 方法可能被多次调用
2.2.6.1 如果 promise 变成了 fulfilled 态,所有的 onFulfilled 回调都需要按照 then 的顺序执行
2.2.6.2 如果 promise 变成了 rejected 态,所有的 onRejected 回调都需要按照 then 的顺序执行
2.2.7 then 必须返回一个 promise
- promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 执行 resolutionProcedure(promise2, x)
2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2 需要被 reject
2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以 promise1 的值 fulfilled
2.2.7.4 如果 onRejected 不是一个函数,promise2 以 promise1 的 reason rejected
2.3 The Promise Resolution Procedure
- resolutionProcedure(promise2, x, resolve, reject)
2.3.1 如果 promise2 和 x 相等,那么 promise 执行 reject TypeError
2.3.2 如果 x 是一个 promsie
2.3.2.1 如果 x 是 pending 状态,那么 promise 必须要保持 pending 状态直到 x 变成 fulfilled 或者 rejected 状态
2.3.2.2 如果 x 是 fulfilled 状态, fulfill promise with the same value
2.3.2.3 如果 x 是 rejected 状态, reject promise with the same reason
2.3.3 如果 x 是一个 object 或者 是一个 function
2.3.3.1 let then = x.then.
2.3.3.2 如果 x.then 这步出错,那么 reject promise with e as the reason
2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromise, rejectPromise)
2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行
resolutionProcedure(promise2, y, resolve, reject);
2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r.
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
2.3.3.3.4 如果调用then抛出异常 e
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
2.3.3.3.4.2 否则,reject promise with e as the reason
2.3.3.4 如果 then 不是一个function. fulfill promise with x.
2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x.
Promise 源码实现
接下来我们只要按照规范来实现 Promise 就行了,很显然规范后半部分看起来是比较复杂的,尤其是 resolutionProcedure 函数的实现,但其实这只是一些实现的细节而已。初看可能不是那么顺畅,那么强烈建议多看几遍规范,然后自己多实现几遍。
1. promise 需要传递一个 executor 执行器,执行器立刻执行
2. 执行器 executor 接受两个参数,分别是 resolve 和 reject
3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled,状态一旦确认,就不会再改变
4. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected
6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将 promise 的值作为参数传递进去。 如果 promise 已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。如果 promise 的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行。
7. then 的参数 onFulfilled 和 onRejected 可以缺省
8. promise 可以 then 多次,promise 的 then 方法返回一个新的 promise
9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调 onFulfilled
10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调 onRejected
11. 如果 then 返回的是一个 promise, 那么需要等这个 promise,那么会等这个 promise 执行完。promise 如果成功, 就走下一个 then 的成功,如果失败,就走下一个 then 的失败
- // 定义三种状态的常量
- const PENDING = 'pending';
- const FULFILLED = 'fulfilled';
- const REJECTED = 'rejected';
- // pomise 接收一个 executor 执行器,执行器立刻执行
- function MyPromise(executor) {
- const _this = this;
- // promise 当前的状态
- _this.currentState = PENDING;
- _this.value = undefined;
- // 保存 then 中的回调,只有当 promise 状态为 pending 时才会缓存,并且每个实例至多缓存一个
- _this.onFulfilledCallbacks = [];
- _this.onRejectedCallbacks = [];
- function resolve(value) {
- if (value instanceof MyPromise) {
- // 如果 value 是个 Promise,调用 then 方法继续执行
- value.then(resolve, reject);
- }
- // 异步执行,保证执行顺序
- setTimeout(() => {
- if (_this.currentState === PENDING) {
- _this.currentState = FULFILLED;
- _this.value = value;
- _this.onFulfilledCallbacks.forEach(fn => fn());
- }
- });
- }
- function reject(reason) {
- // 异步执行,保证执行顺序
- setTimeout(() => {
- if (_this.currentState === PENDING) {
- _this.currentState = REJECTED;
- _this.value = reason;
- _this.onRejectedCallbacks.forEach(fn => fn());
- }
- });
- }
- try {
- executor(resolve, reject);
- } catch(err) {
- reject(err);
- }
- }
- MyPromise.prototype.constructor = MyPromise;
- MyPromise.prototype.then = function (onFulfilled, onRejected) {
- const _this = this;
- // 2.2.1 onFulfilled 和 onRejected 都是可选参数
- // 2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
- // 2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以promise1的值fulfilled
- // 2.2.7.4 如果 onRejected 不是一个函数,promise2 以promise1的reason rejected
- onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : valve => valve;
- onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
- // 2.2.7,then 必须返回一个新的 promise
- const promise2 = new MyPromise((resolve, reject) => {
- if (_this.currentState === FULFILLED) {
- // 2.2.4 保证 onFulfilled,onRjected 异步执行
- setTimeout(() => {
- try {
- // 2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolutionProcedure
- const x = onFulfilled(_this.value);
- resolutionProcedure(promise2, x, resolve, reject);
- } catch(err) {
- // 2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 err, promise2 需要被 reject
- reject(err);
- }
- });
- }
- if (_this.currentState === REJECTED) {
- setTimeout(() => {
- try {
- // 2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolutionProcedure
- const x = onRejected(_this.value);
- resolutionProcedure(promise2, x, resolve, reject);
- } catch(err) {
- // 2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 err, promise2 需要被 reject
- reject(err);
- }
- });
- }
- if (_this.currentState === PENDING) {
- _this.onFulfilledCallbacks.push(() => {
- setTimeout(() => {
- try {
- const x = onFulfilled(_this.value);
- resolutionProcedure(promise2, x, resolve, reject);
- } catch(err) {
- reject(err);
- }
- });
- });
- _this.onRejectedCallbacks.push(() => {
- setTimeout(() => {
- try {
- const x = onRejected(_this.value);
- resolutionProcedure(promise2, x, resolve, reject);
- } catch(err) {
- reject(err);
- }
- });
- });
- }
- });
- return promise2;
- }
- // 2.3 resolutionProcedure(promise2, x, resolve, reject)
- function resolutionProcedure(promise2, x, resolve, reject) {
- // 2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError
- if (promise2 === x) {
- reject(new TypeError('Error'));
- }
- // 2.3.2 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
- if (x instanceof MyPromise) {
- if (x.currentState === PENDING) {
- x.then(value => {
- resolutionProcedure(promise2, value, resolve, reject);
- }, reject)
- } else {
- x.then(resolve, reject);
- }
- }
- // 2.3.3.3.3 reject 或者 resolve 其中一个执行过的话,忽略其他的
- let called = false;
- // 2.3.3,判断 x 是否为对象或者函数
- if ( x && (typeof x === 'object' || typeof x === 'function') ) {
- try {
- let then = x.then;
- // 2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromise, rejectPromise)
- if (typeof then === 'function') {
- then.call(
- x,
- y => {
- if (called) return;
- called = true;
- // 2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行 resolutionProcedure(promise2, y, resolve, reject);
- resolutionProcedure(promise2, y, resolve, reject);
- },
- r => {
- if (called) return;
- called = true;
- // 2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r.
- reject(r);
- }
- );
- } else {
- // 2.3.3.4 如果 then 不是一个 function. fulfill promise with x.
- resolve(x);
- }
- } catch(err) {
- // 2.3.3.2 如果 x.then 这步出错,那么 reject promise with err as the reason
- if (called) return;
- called = true;
- reject(err)
- }
- } else {
- // 2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x.
- resolve(x);
- }
- }
- module.exports = MyPromise;
测试
promises-aplus-tests 这个 npm 包可以帮助我们测试所编写的 promise 代码是否符合 Promises/A+ 的规范。
不过我们需要先增加以下代码去提供测试的接口
- MyPromise.defer = MyPromise.deferred = function () {
- let dfd = {};
- dfd.promise = new MyPromise((resolve, reject) => {
- dfd.resolve = resolve;
- dfd.reject = reject;
- });
- return dfd;
- }
通过 npm 安装
- npm install promises-aplus-tests --save-dev
通过执行 promises-aplus-tests target.js 可以执行测试,promises-aplus-tests 中共有 872 条测试用例。
可以在我的 github 进行查看,通过 npm run test 来执行测试,可以通过所有的测试用例。
更多精彩内容,欢迎关注微信公众号~
手写 Promise 符合 Promise/A+规范的更多相关文章
- 一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise
本文是一起学习造轮子系列的第一篇,本篇我们将从零开始写一个符合Promises/A+规范的promise,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Pr ...
- 只会用就out了,手写一个符合规范的Promise
Promise是什么 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果.从语法上说,Promise 是一个对象,从它可以获取异步操作的消息.Prom ...
- 手写简易版Promise
实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了. ...
- 手写简单的promise
function Promise(fn) { var that = this; this.status = "pedding"; this.value = undefined; / ...
- 一起手写吧!promise.all
Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法 ...
- 一起手写吧!Promise!
1.Promise 的声明 首先呢,promise肯定是一个类,我们就用class来声明. 由于new Promise((resolve, reject)=>{}),所以传入一个参数(函数),秘 ...
- 手写一款符合Promise/A+规范的Promise
手写一款符合Promise/A+规范的Promise 长篇预警!有点长,可以选择性观看.如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的.主要 ...
- 手写Promise A+ 规范
基于ES6语法手写promise A+ 规范,源码实现 class Promise { constructor(excutorCallBack) { this.status = 'pending'; ...
- 手写基于Promise A+规范的Promise
const PENDING = 'pending';//初始态const FULFILLED = 'fulfilled';//初始态const REJECTED = 'rejected';//初始态f ...
随机推荐
- LibreOJ β Round #2」贪心只能过样例
题目友链:https://loj.ac/problem/515 话说这题蛮简单,bitset暴力直接过. 话不多说,上代码! #include <bits/stdc++.h> using ...
- Windows Server 2008 配置 PHP 环境
在配置PHP环境之前要先配置好IIS. 传送门-> Win2008 Server下配置安装IIS 如果IIS是以默认的配置安装,则还需要安装CGI. 在这里勾选CGI,然后安装. 创建一个网站 ...
- Django 学习笔记1-- URLconf
今天好像巴黎有点乱,希望明天太阳还会照常升起. 简介 Django 是一个由 Python 编写.开源并采用经典的 MVC 设计模式的 Web Full Stack 应用框架. 在 Django 中, ...
- 产品需求说明书 PRD模版
XXX产品需求说明书 [版本号:V+数字] 编 制: 日 期: 评 审: 日 期: 批 准: 日 期: 修订记录 版本 修订章节 修订内容 ...
- java反序列化-ysoserial-调试分析总结篇(2)
前言: 这篇主要分析commonCollections2,调用链如下图所示: 调用链分析: 分析环境:jdk1.8.0 反序列化的入口点为src.zip!/java/util/PriorityQueu ...
- Android中使用AsyncTask
>##今天写作业用到了AnsyncTask,记录一下自己的使用情况 >###1.Android.os.AsyncTask类 > 1.AsyncTask类对线程间通讯进行了包装,我们 ...
- webpack中打包拷贝静态文件CopyWebpackPlugin插件
copyWebpackPlugin: 作用:用于webpack打包时拷贝文件的插件包 安装:npm install copyWebpackPlugin@版本号 使用:// copy custom st ...
- Mysql8以上需要指定时区serverTimezone
JDBC连接Mysql8以下 com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test?useUnicode=true&charac ...
- Slog27_支配vue框架初阶项目之博客网站-样式居中
ArthurSlog SLog-27 Year·1 Guangzhou·China July 30th 2018 GitHub 掘金主页 简书主页 segmentfault 没有写够足够的代码量,想成 ...
- 关于使用map存放数据乱序”问题“
今天做项目中遇到了一个比较低级的错误,如果没注意将会变的更麻烦... 其实吧,也不难,要求就是将list中的值转为map后,再顺序输出map中的值,list的顺序怎样,加入到map的顺序也应怎样,不能 ...