写这篇的文章的缘由是在公司内部的前端小组里面分享了一下关于Promise的实现。感受内容还不错,因此在这里分享给你们。源码文件会放到Github上面,感兴趣的同窗能够去查看源码。javascript
Promise
的核心思想是Promise
表示异步操做的结果。一个Promise
处于如下三种状态之一:前端
pending
- Promise
的初始化状态fulfilled
- 表示 Promise
成功操做的状态rejected
- 表示 Promise
错误操做的状态Promise
的内部状态改变如图所示:java
在Promise
没有出现以前,咱们会看到不少相似的代码。git
const fs = require('fs')
fs.readFile('./data.txt','utf8',function(err,data){
fs.readFile(data, 'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data);
})
})
})
复制代码
Promise
出现以后,就能够采用链式调用的形式来写。github
const fs = require('fs')
const readFile = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err);
resolve(data);
})
})
}
readFile('./data.txt').then((data) => {
return readFile(data)
}).then((data) => {
return readFile(data)
}).then((data) => {
console.log(data);
})
复制代码
使用了Promise
以后代码风格变得优雅了不少,写法上也更加直观。typescript
Promise.all
的出现让咱们能够更加方便的处理多个任务完成时在进行处理的逻辑。json
Promise
1.在动手写代码以前先了解一下须要实现哪些功能。promise
Promise constructor
new Promise
时,构造函数须要传入一个executor()
执行器,executor
函数会当即执行,而且它支持传入两个参数,分别是resolve
和reject
。markdownclass Promise<T> { constructor(executor: (resolve: (value: T ) => void, reject: >(reason?: any) => void) => void){ } } 复制代码
Promise
状态 「Promise/A+ 2.1」
Promise
必须处于如下三种状态之一:并发
pending
(等待中),能够转换为fulfilled
(完成)或rejected
(拒绝)。当状态从
pending
切换到fulfilled
时,该状态不得再过渡到其它状态,而且必须具备一个值,该值不能更改。当状态从
pending
切换到rejected
时,该状态不得再过渡到其它状态,而且必须有一个失败的缘由,不能更改。
Promise then
方法 「Promise/A+ 2.2」
Promise
必须有一个then
方法,then
接收两个参数,分别是成功时的回调onFulfilled
, 和失败时的回调onRejected
。
onFulfilled
和onRejected
是可选的参数,而且若是传入的onFulfilled
和onRejected
不是函数的话,则必须将其忽略。若是
onfulfilled
是一个函数。则它必须在Promise
的状态变成fulfilled
(完成)时才能调用,Promise
的值是传进它的第一个参数。而且它只能被调用一次。若是
onRejected
是一个函数,则它必须在Promise
的状态为 rejected(失败)时调用,并把失败的缘由传入它的第一个参数。只能被调用一次。
既然知道了须要实现那些功能,那就来动手操做一下,代码以下:
// 使用枚举定义Promise的状态
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
// 保存当前状态
private status = PROMISE_STATUS.PENDING
// 保存resolve的值,或者reject的缘由
private value: T
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
// 根据规范完成简易功能的then方法
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
if (this.status === PROMISE_STATUS.FULFILLED) {
// 状态为fulfilled时调用成功的回调函数
onfulfilled(this.value)
}
if (this.status === PROMISE_STATUS.REJECTED) {
// 状态为rejected时调用失败的回调函数
onrejected(this.value)
}
}
// 传入executor方法的第一个参数,调用此方法就是成功
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
// 只有是pending状态才能够更新状态,防止二次调用
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
}
// 传入executor方法的第二个参数,调用此方法就是失败
private _reject = (value) => {
// 只有是pending状态才能够更新状态,防止二次调用
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
}
}
复制代码
代码写完了咱们测试一下功能:
const p1 = new _Promise((resolve, reject) => {
resolve(2)
})
p1.then(res => {
console.log(res, 'then ok1')
})
const p2 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
p2.then(res => {
console.log(res, 'then ok2')
})
复制代码
控制台会打印出:
2 "then ok1"
复制代码
不错,如今已是稍见雏形。
咱们已经实现了一个入门级的 Promise
,可是细心的同窗应该已经发现了,then ok2
这个值没有打印出来。
致使这个问题出现的缘由是什么呢?原来是咱们在执行then函数的时候,因为是异步操做,状态一直处于pending的状态,传进来的回调函数没有触发执行。
知道了问题就好解决了。只须要把传进来的回调函数存储起来。在调用resolve或reject方法的时候执行就能够了,咱们优化一下代码:
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
private status = PROMISE_STATUS.PENDING
private value: T
// 保存then方法传入的回调函数
private callbacks = []
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
// 把then方法传入的回调函数整合一下
const handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
onfulfilled && onfulfilled(this.value)
}
if (this.status === PROMISE_STATUS.REJECTED) {
onrejected && onrejected(this.value)
}
}
if (this.status === PROMISE_STATUS.PENDING) {
// 当状态是pending时,把回调函数保存进callback里面
this.callbacks.push(handle)
}
handle()
}
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
// 遍历执行回调
this.callbacks.forEach(fn => fn())
}
private _reject = (value) => {
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
// 遍历执行回调
this.callbacks.forEach(fn => fn())
}
}
复制代码
在来测试一下上面的代码:
const p2 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
p2.then(res => {
console.log(res, 'then ok2')
})
复制代码
在等待1s后,控制台会打印出:
2 "then ok2"
复制代码
目前已经能够支持异步操做了。如今的你已是江湖中的高手了。
在文章一开头介绍Promise时,提到了链式调用的概念.then().then()
,如今就要实现这个相当重要的功能,在开始前先看一下Promise/A+
的规范
then
必须返回一个Promise 「Promise/A+ 2.2.7」
promise2 = promise1.then(onFulfilled, onRejected);
若是一个
onFulfilled
或onRejected
返回一个值x
,则运行Promise Resolution Procedure
(会在下面实现这个方法)。若是任何一个
onFulfilled
或onRejected
引起异常e
则promise2
必须以e
为其理由reject
(拒绝).若是
onFulfilled
不是函数且promise1
状态已经fuifilled
(完成),则promise2
必须使用与相同的值来实现promise1
。若是
onRejected
不是函数而promise1
状态为rejected
(拒绝),则promise2
必须以与相同的理由将其拒绝promise1
。Promise Resolution Procedure 实现
首先该方法的使用方式相似于下面这种形式
resolvePromise(promise,x,...)
若是
promise
和x
引用相同的对象,promise 则应该以TypeError为理由拒绝。「Promise/A+ 2.3.1」若是
x
是一个promise
,则应该采用它本来的状态返回。「Promise/A+ 2.3.2」不然,判断
x
若是是对象或者是函数。则执行如下操做,先声明let then = x.then
,若是出现异常结果e
,则以e
做为promise
reject(拒绝)的缘由。若是then
是个函数,则用call
执行then
,把this
指向为x
,第一个参数用resolvePromise
调用,第二个用rejectPromise
调用「Promise/A+ 2.3.3」若是
x
不是对象或者方法,则使用x
的值resolve
完成 「Promise/A+ 2.3.4」
只是经过文字不太容易理解,咱们来看一下代码的实现:
enum PROMISE_STATUS {
PENDING,
FULFILLED,
REJECTED
}
class _Promise<T> {
private status = PROMISE_STATUS.PENDING
private value: T
private callbacks = []
constructor(executor: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
executor(this._resolve, this._reject)
}
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
const nextPromise = new _Promise((nextResolve, nextReject) => {
const handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
const x = (onfulfilled && onfulfilled(this.value))
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
}
if (this.status === PROMISE_STATUS.REJECTED) {
if (onrejected) {
const x = onrejected(this.value)
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
} else {
nextReject(this.value)
}
}
}
if (this.status === PROMISE_STATUS.PENDING) {
this.callbacks.push(handle)
} else {
handle()
}
});
return nextPromise
}
private _resolve = (value) => {
if (value === this) {
throw new TypeError('A promise cannot be resolved with itself.');
}
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.FULFILLED;
this.value = value;
this.callbacks.forEach(fn => fn())
}
private _reject = (value) => {
if (this.status !== PROMISE_STATUS.PENDING) return;
this.status = PROMISE_STATUS.REJECTED;
this.value = value
this.callbacks.forEach(fn => fn())
}
private _resolvePromise = <T>(nextPromise: _Promise<T>, x: any, resolve, reject) => {
// 2.3.1 nextPromise 不能和 x 相等
if (nextPromise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
// 2.3.2 若是 x 是 Promise 返回 x 的状态和值
if (x instanceof _Promise) {
x.then(resolve, reject)
}
// 2.3.3 若是 x 是对象或者函数执行 if 里面的逻辑
if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x);
}
// 2.3.3.1
let then;
try {
then = x.then;
} catch (error) {
return reject(error);
}
// 2.3.3.3
if (typeof then === 'function') {
// 声明called 在调用过一次resolve或者reject以后,修改成true,保证只能调用一次
let called = false;
try {
then.call(x, y => {
if (called) return; // 2.3.3.3.4.1
called = true;
// 递归解析的过程(由于可能 promise 中还有 promise)
this._resolvePromise(nextPromise, y, resolve, reject)
}, r => {
if (called) return; // 2.3.3.3.4.1
called = true;
reject(r)
})
} catch (e) {
if (called) return; // 2.3.3.3.4.1
// 2.3.3.3.4
reject(e)
}
} else {
// 2.3.3.4
resolve(x)
}
} else {
// 2.3.4
resolve(x);
}
}
}
复制代码
目前已经实现能够链式调用的功能了,咱们来测试一下:
const p3 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 1000);
})
p3.then(res => {
console.log(res, 'then ok3')
return '链式调用'
}).then(res => {
console.log(res)
})
复制代码
等待1s以后,控制台会打印出:
3 "then ok3"
"链式调用"
复制代码
有没有同窗想到还缺乏了一个尤其重要的功能,那就是微任务。咱们应该如何实现和内置 Promise
同样的微任务流程呢?
在 Web Api
里面有这样一个方法 MutationObserver。咱们能够基于它实现微任务的功能。而且也已经有相关的库给咱们封装好了这个方法,它就是 asap。只要把须要以微任务执行的函数传入便可。
asap(function () {
// ...
});
复制代码
其实在 Web Api
里面还有这样一个方法 queueMicrotask 能够直接使用。使用方式也是把要以微任务执行的函数传入进去便可。
self.queueMicrotask(() => {
// 函数的内容
})
复制代码
queueMicrotask
惟一的缺点就是兼容性不太好,在生产环境中建议仍是使用 asap
这个库来实现微任务。
把以前写好的 Promise then
方法稍微作一下调整:
then(onfulfilled: (value: T) => any, onrejected: (value: any) => any) {
// 2.2.1
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : null;
onrejected = typeof onrejected === 'function' ? onrejected : null;
const nextPromise = new _Promise((nextResolve, nextReject) => {
const _handle = () => {
if (this.status === PROMISE_STATUS.FULFILLED) {
const x = (onfulfilled && onfulfilled(this.value))
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
}
if (this.status === PROMISE_STATUS.REJECTED) {
if (onrejected) {
const x = onrejected(this.value)
this._resolvePromise(nextPromise, x, nextResolve, nextReject)
} else {
nextReject(this.value)
}
}
}
const handle = () => {
// 支持微任务
queueMicrotask(_handle)
}
if (this.status === PROMISE_STATUS.PENDING) {
this.callbacks.push(handle)
} else {
handle()
}
});
return nextPromise
}
复制代码
如今完美支持微任务,和内置 Promises
事件执行顺序一致。咱们来测试一下:
console.log('first')
const p1 = new _Promise(function (resolve) {
console.log('second')
resolve('third')
})
p1.then(console.log)
console.log('fourth')
复制代码
能够看到控制台打印的结果为:
first
second
fourth
third
复制代码
到这里,咱们已经把Promise最关键的功能完成了:支持异步操做
,then支持链式调用
,支持微任务
。
1.下载Promise/A+规范提供了一个专门的测试脚本 promises-aplus-tests
yarn add promises-aplus-tests -D
复制代码
2.在咱们的代码中加入如下代码:
(_Promise as any).deferred = function () {
let dfd = {} as any;
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = _Promise;
复制代码
3.修改 package.json
文件增长如下内容(./dist/promise/index.js
是须要测试的文件路径):
{
"scripts": {
"test": "promises-aplus-tests ./dist/promise/index.js"
}
}
复制代码
4.执行 yarn test
能够看到872个测试用例所有经过。
Promise
的其它 API
实现到目前为止,上述代码已经完整的按照 Promise/A+ 规范实现了,但还有一些内置Api没有实现。下面就把这些内置的方法来实现:
class _Promise {
catch(onrejected) {
return this.then(null, onrejected)
}
finally(cb) {
return this.then(
value => _Promise.resolve(cb()).then(() => value),
reason => _Promise.resolve(cb()).then(() => { throw reason })
);
}
static resolve(value) {
if (value instanceof _Promise) {
return value;
}
return new _Promise(resolve => {
resolve(value);
});
}
static reject(reason) {
return new _Promise((resolve, reject) => {
reject(reason);
});
}
static race(promises) {
return new _Promise(function (resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promise.race accepts an array'));
}
for (var i = 0, len = promises.length; i < len; i++) {
_Promise.resolve(promises[i]).then(resolve, reject);
}
});
}
static all(promises) {
let result = [];
let i = 0;
return new _Promise((resolve, reject) => {
const processValue = (index, value) => {
result[index] = value;
i++;
if (i == promises.length) {
resolve(result);
};
};
for (let index = 0; index < promises.length; index++) {
promises[index].then(value => {
processValue(index, value);
}, reject);
};
});
}
static allSettled(promises) {
let result = []
let i = 0;
return new _Promise((resolve, reject) => {
const processValue = (index, value, status: 'fulfilled' | 'rejected') => {
result[index] = { status, value }
i++;
if (i == promises.length) {
resolve(result);
};
};
for (let index = 0; index < promises.length; index++) {
promises[index].then(value => {
processValue(index, value, 'fulfilled')
}, value => {
processValue(index, value, 'rejected')
});
};
})
}
...
}
复制代码
async
和 await
咱们完成了 Promise
的实现。可是你们有没有想过 async
和 await
这个 Promise
的语法糖如何实现呢?
这里咱们就要借助 Generator
函数来实现这个功能,废话少说,直接上代码:
let gp1 = new _Promise(r => {
setTimeout(() => {
r(1)
}, 1000);
})
let gp2 = new _Promise(r => {
setTimeout(() => {
r(2)
}, 1000);
})
function* gen() {
let a = yield gp1
let b = yield gp2
return b + a
}
function run(gen) {
return new _Promise(function (resolve, reject) {
g = gen()
function next(v) {
ret = g.next(v)
if (ret.done) return resolve(ret.value);
_Promise.resolve(ret.value).then(next)
}
next()
})
}
run(gen).then(console.log)
复制代码
控制台里面会打印出结果为:3
若是对这个 run
函数感兴趣,推荐去看下这个 co 库实现,代码写的很是简洁,只有一百行左右,值的一看。
用了近半天的时间才把这篇文章给写出来。其中的源码文件已经放到 Github 上面。不想手敲一遍的同窗能够直接拉下来代码执行查看结果。若是你有不一样的意见或想法也欢迎留言。
相关资源连接