最开始是在一个微信公众号上看到一篇文章,有对于手写 Promise
的局部内容,感觉很离奇,也是个挑战,遂本人也想尝试下,区别就是人家是一个文章列举的的罕用手写 JS
汇合,Promise
只是其中一小块,我要独自把它拎进去讲讲,哈哈
公众号文章
文章地址
基本概念
1.Promise
Promise
是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。它由社区最早提出和实现,ES6
将其写进了语言规范,对立了用法,原生提供了 Promise
对象。
所谓Promise
,简略说就是一个容器,外面保留着某个将来才会完结的事件(通常是一个异步操作)的后果。从语法上说,Promise
是一个对象,从它能够获取异步操作的音讯。Promise
提供对立的 API
,各种异步操作都能够用同样的办法进行解决。
以上出自阮一峰的 ECMAScript 6 入门
2.Promises/A+
Promises/A+
又是啥?上面是来自 Promises/A+
官网的一句话
An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
翻译成人话就是说 Promises/A+
是 JavaScript
Promise
的凋谢规范,Promise
的实现都要遵循这个最根本的规范,咱们平时熟知的 ES6
Promise
就是完全符合 Promises/A+
标准的,然而它们又不完全相同,ES6
Promise
上补充了实例上的 catch
、finally
,静态方法all
、resolve
以及 reject
等等
3.Promises/A+ 讲些啥?
最开始我是想把 Promises/A+
的规定全副列举进去的,起初想了一下,如同没啥必要,又臭又长。。。大家有趣味的能够本人的去看一下,我感觉英文原文次要参考一下,用谷歌翻译也能大抵根本都翻译精确,但还是有些许翻译的不到位,这里就举荐去看他人翻译的现成的了,我这边找的一个他人的翻译,集体感觉翻译的还听好的,还有相应的正文(Promises/A+
翻译)
手写 Promise
把 Promises/A+
的规定熟读几遍后,就能够开始本人尝试手写 Promise,我这边先放出我本人的手写实现,是用 class
写的,我是感觉用 class
是比拟精炼的,容易了解,当然你也能够用构造函数、IIFE 啥的,都能够
手写 Promise 代码
const CustomPromise = class {
// 定义一个动态的全副状态的 map
static STATUS_MAP = {
Pending: 'Pending',
Fulfilled: 'Fulfilled',
Rejected: 'Rejected',
}
// Promise 的状态
status = CustomPromise.STATUS_MAP.Pending
// then 办法传入的 onfulfilled 函数组成的列表
onfulfilled = []
// then 办法传入的 onrejected 函数组成的列表
onrejected = []
result = undefined
reason = undefined
// then 办法返回的 Promise 的参数 executor 的回调函数 resolve 组成的列表
resolve = []
// then 办法返回的 Promise 的参数 executor 的回调函数 reject 组成的列表
reject = []
// then 办法返回的 Promise 列表
promises = []
/**
* 构造函数
* @param {function} executor Promise 执行器
* @returns
*/
constructor (executor) {if (typeof executor === 'undefined' || typeof executor !== 'function') {throw new TypeError('CustomPromise resolver is not a function')
}
/**
* 设置胜利的 result 以及程序执行 onfulfilled 函数
* @param {*} result
*/
const setResult = (result) => {
this.result = result
this.status = CustomPromise.STATUS_MAP.Fulfilled
if (this.onfulfilled.length > 0) {this.onfulfilled.forEach((onfulfilled_item, index) => {this.excuteOnfulfilled(onfulfilled_item, index, this.result)
})
}
}
/**
* 设置失败的 reason 以及程序执行 onrejected 函数
* @param {*} result
*/
const setReason= (reason) => {
this.reason = reason
this.status = CustomPromise.STATUS_MAP.Rejected
if (this.onrejected.length > 0) {this.onrejected.forEach((onrejected_item, index) => {this.excuteOnrejected(onrejected_item, index, this.reason)
})
}
}
try {const resolve = (result) => {if (this.status === CustomPromise.STATUS_MAP.Pending) { // Promise 外部状态具备凝固成果,一但确定了就不再发生变化
if (result !== null && (typeof result === 'function' || typeof result === 'object')) {
let called = false
try {const { then} = result // resolve 办法能够承受一个 thenable 对象
if (typeof then === 'function') {const then_ = then.bind(result)
then_(res => {if (called) return // 确保 thenable 对象 then 办法的 resolvePromise 回调函数只执行一次
called = true
setResult(res)
}, err => {if (called) return
called = true
setReason(err)
})
} else {setResult(result)
}
} catch (error) {if (called) return
setReason(error)
}
} else {setResult(result)
}
}
}
const reject = (reason) => {if (this.status === CustomPromise.STATUS_MAP.Pending) {setReason(reason)
}
}
const executor_ = executor.bind(null, resolve, reject) // 为执行器绑定参数
executor_() // 执行器执行(同步)} catch (e) {if (this.status === CustomPromise.STATUS_MAP.Fulfilled || this.status === CustomPromise.STATUS_MAP.Rejected) return
setReason(e)
}
}
/**
* then 办法
* @param {function} onfulfilled
* @param {function} onrejected
* @returns
*/
then (onfulfilled, onrejected) {this.onfulfilled.push(onfulfilled)
if (this.status === CustomPromise.STATUS_MAP.Fulfilled) { // Promise 对象在状态凝固之后依然是能够调用 then 办法的
this.onfulfilled.forEach((item, index) => {if (item === onfulfilled) {this.excuteOnfulfilled(item, index, this.result)
}
})
}
this.onrejected.push(onrejected)
if (this.status === CustomPromise.STATUS_MAP.Rejected) {this.onrejected.forEach((item, index) => {if (item === onrejected) {this.excuteOnrejected(item, index, this.reason)
}
})
}
const customPromise = new CustomPromise((resolve, reject) => {this.resolve.push(resolve)
this.reject.push(reject)
})
this.promises.push(customPromise)
return customPromise // then 办法返回新的 Promise 对象
}
/**
* 执行 onfulfilled 函数
* @param {function} onfulfilled
* @param {number} index
* @param {*} result
*/
excuteOnfulfilled (onfulfilled, index, result) {if (typeof onfulfilled === 'function') {setTimeout(() => {
let x = null
try {x = onfulfilled(result)
} catch (error) {this.reject[index](error)
}
if (x === this.promises[index]) {this.reject[index](new TypeError('[onFulfilled] return the same value with [then] function'))
}
this.resolutionProcedure(x, this.promises[index], this.resolve[index], this.reject[index])
}, 0)
} else {if (this.status === CustomPromise.STATUS_MAP.Fulfilled) {setTimeout(() => {this.resolve[index](result)
}, 0)
}
}
}
/**
* 执行 onrejected 函数
* @param {function} onrejected
* @param {number} index
* @param {*} reason
*/
excuteOnrejected (onrejected, index, reason) {if (typeof onrejected === 'function') {setTimeout(() => {
let x = null
try {x = onrejected(reason)
} catch (error) {this.reject[index](error)
}
if (x === this.promises[index]) {this.reject[index](new TypeError('[onrejected] return the same value with [then] function'))
}
this.resolutionProcedure(x, this.promises[index], this.resolve[index], this.reject[index])
}, 0)
} else {if (this.status === CustomPromise.STATUS_MAP.Rejected) {setTimeout(() => {this.reject[index](reason)
}, 0)
}
}
}
/**
* Promise 解决过程(重点)* @param {*} x then 办法回调函数 resolvePromise 执行后返回的值
* @param {CustomPromise} promise then 办法返回的 Promise
* @param {function} resolve then 办法返回的 Promise 的参数 executor 的回调函数 resolve
* @param {function} reject then 办法返回的 Promise 的参数 executor 的回调函数 reject
* @returns
*/
resolutionProcedure (x, promise, resolve, reject) {if (x instanceof CustomPromise) {
x.then(res => {resolve(res)
}, err => {reject(err)
})
} else if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
let called = false
try {const { then} = x // then 办法回调函数 resolvePromise 执行后返回的值是一个 thenable 对象,执行 then 办法
if (typeof then === 'function') {const then_ = then.bind(x)
const resolvePromise = y => {if (called) return // 确保 resolvePromise 只执行一次
called = true
// then 办法回调函数 resolvePromise 执行后返回的值是一个 thenable 对象,执行 then 办法后,如果 then 办法的 resolvePromise 参数被回调
// 对 resolvePromise 参加回调的参数 y 继续执行 Promise 解决过程,也就是调用 resolutionProcedure 办法
this.resolutionProcedure(y, promise, resolve, reject)
}
const rejectPromise = r => {if (called) return
called = true
reject(r)
}
then_(resolvePromise, rejectPromise)
} else {resolve(x)
}
} catch (error) {if (called) return
reject(error)
}
} else {resolve(x)
}
}
/**
* 动态的 resolved 办法,返回一个曾经胜利的 Promise
* @param {*} result
* @returns
*/
static resolved (result) {return new CustomPromise((resolve, reject) => {if (result !== null && (typeof result === 'function' || typeof result === 'object')) {
let called = false
try {const { then} = result
if (typeof then === 'function') {const then_ = then.bind(result)
then_(res => {if (called) return
called = true
resolve(res)
}, err => {
called = true
reject(err)
})
} else {resolve(result)
}
} catch (error) {if (called) return
reject(error)
}
} else {resolve(result)
}
})
}
/**
* 动态的 rejected 办法,返回一个曾经失败的 Promise
* @param {*} result
* @returns
*/
static rejected (reason) {return new CustomPromise((resolve, reject) => {reject(reason)
})
}
/**
*
* @returns 测试用
*/
static deferred () {const result = {};
result.promise = new CustomPromise(function(resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
}
module.exports = CustomPromise;
手写 Promise 注意事项
先列出一个典型的应用 Promise
的规范代码,上面有些术语会已这个为准
const p = new Promise((resolve, reject) => {if (xxx) {resolve()
} else {reject(new TypeError('error'))
}
})
// thenable 对象
const thenable = (val) => {
return {then: (resolvePromise, rejectPromise) => {
// balabala
rejectPromise(val)
}
}
}
const onFulfilled = (res) => {return x}
const onRejected = (err) => {}
const p1 = p.then(onFulfilled, onRejected)
这里列几个我感觉在手写 Promise
很容易疏忽的点
Promise
外部状态具备凝固成果,一但确定了就不再发生变化Promise
的构造函数是同步执行的Promise
外部的resolve
执行是也是同步的,然而Promise
在被应用时,resolve
可能是被同步调用也可能是被异步调用,这个要留神。resolve
如果是被同步调用的话,then
办法执行的时候就要立刻执行onFulfilled
以及onRejected
了,resolve
如果是被异步调用的话,then
办法会先执行,须要把onFulfilled
以及onRejected
暂存起来,等到resolve
被调用的时候再执行。resolve
能够传入一个thenable
对象,如果是thenable
对象,须要执行如上面所示代码的操作(调用它的then
办法)onFulfilled
以及onRejected
在Promise
外部须要被异步调用(这里权且先间接了解为异步就能够,深刻的讲还波及宏工作微工作,有趣味的能够去理解下,这里我是间接应用setTimeout
实现异步的,是能够顺利通过测试的then
能够被屡次调用(p.then();p.then();
),也能够链式调用(p.then().then();
),每次 then 办法返回的都是一个新的Promise
,所以Promise
外部设计保留onFulfilled
以及onRejected
的数据结构是数组- 非常简单哔哔一下
Promise
的解决过程:then
办法返回一个Promise
叫p1
,在返回then
的这个Promise
的时候,咱们把结构函数参数executor
的回调函数resolve
以及reject
暂存起来。onFulfilled
如果返回一个thenable
对象(就是下面那个x
)(如果不是thenable
对象间接resolve(x)
),对这个thenable
对象执行如上面所示代码的操作(调用它的then
办法)。如果resolvePromise
被调用了,参数咱们示意为y
, 如果y
是thenable
对象,继续执行上面的操作(如果不是间接resolve(y)
), 就这样始终递归上来,直到遇到y
不是thenable
对象(这只是我本人的简略了解,如果示意看迷糊了还请以Promises/A+
原文为准,尽管我感觉那个看了可能会更迷糊。。。) - 须要部署三个静态方法,动态的
resolved
办法(也能传入thenable
对象),返回一个曾经胜利的Promise
;动态的rejected
办法,返回一个曾经失败的Promise
,动态的deferred
办法,返回一个对象蕴含(一个Promise
对象,新建这个Promise
对象时结构函数参数executor
的回调函数resolve
以及reject
)。resolved
办法以及rejected
办法不强制要求部署 - 重点:结构函数参数
executor
的回调函数resolve
以及动态的resolved
办法都能承受一个thenable
对象作为参数,须要对这个thenable
对象执行如上面所示代码的操作(调用它的then
办法),这是Promises/A+
标准上没有细说的,开始就是疏忽了这个,导致测试没法顺利进行上来
const resolvePromise = (y) => {// balabala}
const rejectPromise = (err) => {// balabala}
thenable.then(resolvePromise, rejectPromise)
利用 promises-tests 对手写 Promise 进线测试
测试步骤很简略
- 装置依赖
npm install promises-aplus-tests --save-dev
package.json
退出脚本
"test": "promises-aplus-tests < 你的手写 Promise JS 门路 >"
- 控制台输出
npm run test
- 期待后果。。。
释怀一开始预计后果都不会太难看,就像我这样。。。😂
到最初😉
排错指南
可能有人苦苦排查,一遍又一遍的查看本人的代码,但每次测试还总是那几个项报错,很是苦恼,想当初我就是啊,这里我就要倡议你去看一下 promises-tests
的源码了(释怀源码很小),正所谓知己知彼百战不殆啊😂,你只有晓得了考试内容才晓得怎么通过考试不是?(如同这个比喻怪怪的😅)
promises-tests 仓库地址
我的项目先克隆下来再关上,看看入口是啥
"bin": "lib/cli.js",,
咱们关上lib/cli.js
发现其主函数就在lib/programmaticRunner.js
promises-tests
用的是 Mocha
进行的测试,测试文件在 lib/tests
下,他会读取测试文件顺次进行测试
你能够依据测试的谬误提醒定位本人失败的测试用例
我是大部分测试都栽在了 2.3.3.3,咱们关上 2.3.3 看看,发现其蕴含一个主的测试用例,其中又有三个测试用例
再顺次关上,直到 2.3.3.3
抽取其中的次要测试代码
- PromiseTest.js
import thenables from './thenables';
import CustomPromise from '../promise/CustomPromise';
const resolved = CustomPromise.resolved;
const rejected = CustomPromise.rejected;
const sentinel = {sentinel: "sentinel"};
const dummy = {dummy: "dummy"};
export default function testMain () {function yFactory() {return thenables.fulfilled['an already-fulfilled promise'](thenables.fulfilled['an asynchronously-fulfilled custom thenable'](sentinel));
}
function xFactory() {
return {then: function (resolvePromise) {const yFactory_ = yFactory()
resolvePromise(yFactory_);
}
};
}
const promise = resolved(dummy).then(function onBasePromiseFulfilled() {const xFactory_ = xFactory()
return xFactory_;
});
const test = function (promise) {promise.then(function onPromiseFulfilled(value) {console.log('最终:', value);
})
}
test(promise)
}
- thenables.js
import CustomPromise from './CustomPromise';
var resolved = CustomPromise.resolved;
var rejected = CustomPromise.rejected;
var deferred = CustomPromise.deferred;
var other = {other: "other"}; // a value we don't want to be strict equal to
const fulfilled = {"a synchronously-fulfilled custom thenable": function (value) {
return {then: function (onFulfilled) {onFulfilled(value);
}
};
},
// 略
};
const rejected_ = {"a synchronously-rejected custom thenable": function (reason) {
return {then: function (onFulfilled, onRejected) {onRejected(reason);
}
};
},
// 略
};
export default {
fulfilled: fulfilled,
rejected: rejected_
}
在 fulfilled
状况下能输入 {sentinel: "sentinel"}
就代表测试通过了,而后就依据这段小型测试代码去一直调试本人的程序,在一直的改良下,就能在一次偶尔的测试中失去 872 passing 了!🤗
手写 Promise 的意义
- 首先就是能通过手写
Promise
对Promise
能有更加深刻的了解,就比方我之前是齐全就不知道onFulfilled
返回thenable
的后续操作的,当然这个实现还很简陋,比方ES6
Promise
实例上的catch
、finally
,静态方法all
、any
以及race
等等都还没实现,后续能够加上 - 其次就是当初的面试动不动就造航母,不学点货色不行啊😷
下期预报
如果上线顺利应该会推下我的小程序,欢送大家试用🤗