Promise
Promise 是 ES6 中异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise 是一个对象,提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
操作只会处在 3 种状态的一种:未完成态(pending
)、完成态(resolved
)、失败态(rejected
)Promise
的状态只会出现从未完成态向完成态或失败态转化Promise
的状态一旦转化,将不能被更改
更多 Promise 基础知识请跳转 阮一峰 - Es6 Promise
手动实现一个Promise
这年头,手动实现一个 Promise
已经是面试常规操作,接下来,为了更好理解 Promise
,我们就来实现一下;Promise
是有统一规范的,目的是为了让大家自己实现的 promise
更好的实现兼容,所以出现了 Promises/A+规范;
接下来的 promise
都是根据Promises/A+这份规范来实现的
Promise 类
因为是有三种状态,所以先声明三个常量来代表状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
写出一个 Promise
类
class Promise {
constructor(executor) {
// 形参校验
if (typeof executor !== 'function') throw new TypeError(`Promise resolver ${executor} is not a function`)
// 初始化
this.status = PENDING; // 状态
this.value = null; // 终值
this.reason = null; // 拒因
// 执行cb
executor();
}
}
然后我们需要为 Promise
的形参executor
增加 resolve
和 reject
两个形参函数;
根据定义,他们是由 JS引擎
提供的,所以我们需要先提前定义好
class Promise {
constructor(executor) {
...
// 将 resolve 和 reject 中的 this 绑定到 Promise 实例
+ this.resolve = this.resolve.bind(this);
+ this.reject = this.reject.bind(this);
// 执行cb
+ try {
+ executor(this.resolve, this.reject);
+ } catch (e) { this.reject(e) }
}
resolve(val) {
// 判断当前状态,只有处于等待状态才可以执行
if (this.status === PENDING) {
this.status = FULFILLED; // 状态变化
this.value = val; // 终值赋值
}
}
reject(err) {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = err;
}
}
}
- 当
promise
实例调用内部函数resolve
或者reject
时,他们被调用时实际上是一个匿名函数,无法取到函数中this.status
的值,所以需要用bind
提前绑定; - 当
promise
实例的executor
内部本身本身抛出异常时,Promise
是会将异常一并放入reject
函数来处理的,所以增加try{...}catch(e){...}
来处理这种情况
初步实现 then
then
接收两个回调函数,onFulfilled
是当 promise
实例状态变为 fulfilled
时调用的函数,onRejected
是实例状态变为 rejected
时调用的函数,并且 then
返回的是一个 新的 promise
实例;
这里用 setTimeout
来模拟 Promise
内部的微任务异步
class Promise {
...
then(onFulfilled, onRejected) {
// 参数校验 onFulfilled, onRejected 为可选形参
onFulfilled = typeof onFulfilled !== 'function' ? val => val : onFulfilled;
onRejected = typeof onRejected !== 'function' ? reason => { throw reason } : onRejected;
if (this.status === FULFILLED) {
// setTimeout 模拟微任务异步
setTimeout(() => {
try { // 重新添加try-catch,settimeout 内为异步,无法被外层constructor中的try-catch捕获
onFulfilled(this.value); // 以终值作为参数执行 onFulfilled 函数
} catch (e) { reject(e) }
})
}
if (this.status === REJECTED) {
// 以拒因作为参数执行 onRejected 函数
setTimeout(() => {
try {
onRejected(this.reason);
} catch (e) { reject(e) }
});
}
}
}
then
的修改
根据上面的写法,如果我们的 promise
实例的 executor
中,resolve
是一个异步实现的话,那么 then
方法是无法实现原来的效果的
new Promise((resolve, reject) => {
setTimeout(() => { resolve(1) })
}).then(
val => console.log(val), // 输出 1
err => console.log(err)
)
此时我们自己的 promise
实例无法正确输出 1
,原因在于,当 executor
中状态改变(resolve
/ reject
)是异步实现时,then
函数是同步被调用的,此时的 promise
的状态还处于 pending
状态,无法调用任何函数;
所以我们需要在 then
中添加对于 pending
状态的处理;采用订阅发布的模式来解决状态的异步改变
class Promise {
constructor(executor) {
...
// 初始化
...
+ this.onFulfilledCbs = []; // 成功回调函数队列
+ this.onRejectedCbs = []; // 失败回调函数队列
...
}
resolve(val) {
if (this.status === PENDING) {
...
// 一旦状态改变,遍历函数队列将终值作为形参触发队列中的函数
+ this.onFulfilledCbs.forEach(fn => fn(this.value));
}
}
reject(err) {
if (this.status === PENDING) {
...
+ this.onRejectedCbs.forEach(fn => fn(this.reason));
}
}
then(onFulfilled, onRejected) {
...
// 当状态处于pending时
+ if (this.status === PENDING) {
+ // 向函数队列添加微任务回调函数
+ this.onFulfilledCbs.push(value => {
+ setTimeout(() => {
+ try {
+ onFulfilled(value)
+ } catch (e) { reject(e) }
+ })
+ });
+
+ this.onRejectedCbs.push((reason) => {
+ setTimeout(() => {
+ try {
+ onRejected(reason)
+ } catch (e) { reject(e) }
+ })
+ });
+ }
}
}
这样当 promise
实例的异步改变状态时,then
方法也能正确的触发
链式调用
之前说了,then
方法会返回一个新的 promise
实例,并且这个实例的值可以随着 then
无限调用而无限传递下去,为了实现这个方法,我们需要在 then
中返回一个新的 promise
实例
then(onFulfilled, onRejected) {
...
// 创建一个新promise实例
+ let promise2 = new Promise((resolve, reject) => {
// 同步操作(最开始的状态改变为同步)
if (this.status === FULFILLED) {
...
}
if (this.status === REJECTED) {
...
}
if (this.status === PENDING) {
...
}
+ })
+ return promise2
}
那我们如何在新的 promise2
中去调用 resolve
和 reject
?
遇事不决,参考规范
根据Promises/A+规范,我们将onFulfilled(value)
和 onRejected(reason)
的结果定义成 x
,并将其和 promise2
传进一个新的函数 resolvePromise
进行处理;
这里我们将这个新的处理函数定义为常函数
// @params
// $pomise2 - 新promise常量
// $x - onFulfilled / onRejected 处理结果
// $resolve - promise2 中的resolve函数
// $reject - promise2 中的reject函数
const RESOLVEPROMISE = function (promise2, x, resolve, reject) { ... }
然后再对 then
方法进行改写
then(onFulfilled, onRejected) {
...
// 返回一个新的实例来实现链式调用
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
// 以终值作为参数执行 onFulfilled 函数
+ let x = onFulfilled(this.value);
// 分析执行结果 x 与 promise 的关系
+ RESOLVEPROMISE(promise2, x, resolve, reject);
} catch (e) { reject(e) }
})
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
+ let x = onRejected(this.reason)
+ RESOLVEPROMISE(promise2, x, resolve, reject);
} catch (e) { reject(e) }
});
}
if (this.status === PENDING) {
this.onFulfilledCbs.push(value => {
setTimeout(() => {
try {
+ let x = onFulfilled(value)
+ RESOLVEPROMISE(promise2, x, resolve, reject);
} catch (e) { reject(e) }
})
});
this.onRejectedCbs.push((reason) => {
setTimeout(() => {
try {
+ let x = onRejected(reason)
+ RESOLVEPROMISE(promise2, x, resolve, reject);
} catch (e) { reject(e) }
})
});
}
})
return promise2
}
实现 resolvePromise
函数
根据规范,resolvePromise
规则大致如下:
promise2
和x
指向同一对象,抛出一个类型错误x
是一个Promise
类,则接受它的状态,并对应执行其resolve
或者reject
函数x
是一个函数或者对象x.then
是一个函数,调用x.then
方法,并且递归结果x.then
不是函数,调用resolve
- 以上都不是,调用
resolve
const RESOLVEPROMISE = function (promise2, x, resolve, reject) {
// x 与 promise2 相等 -> 报错
if (promise2 == x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
let called; // 防止多次调用 成功 和 失败
// x 是否是 promise 类
if (x instanceof Promise) {
x.then(value => {
Promise.resolvePromise(promise2, value, resolve, reject);
}, err => {
reject(err)
})
// x 是函数或者对象
} else if (x !== null && typeof x == 'object' || typeof x == 'function') {
try { // 取 x.then 可能报错
// 如果 x 有 then 方法
let then = x.then;
// 如果 then 是一个函数
if (typeof then == 'function') {
// 用 call 调用 then 方法指向 x,防止再次取 x.then 报错
then.call(x, value => {
if (called) return;
called = true;
Promise.resolvePromise(promise2, value, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err)
})
// then 不是一个函数
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// x 为基本类型值
resolve(x);
}
}
到这里,我们的 Promise
就基本实现了,then
方法以外的方法这里就不一一实现了;
检验
这边可以全局安装一个插件来检验自己实现的 Promise
文件是否符合规范npm install -g promises-aplus-tests
然后在文件最后添加,引用插件的接口来检验
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
最后在终端里输入指令,对该文件进行检验promises-aplus-tests [[你的Promise文件名.js]]
源码
欢迎移步本文源码
最后
最后,一个 符合Promise/A+规范的 Promise
就实现了,希望能帮助大家更清楚的了解 Promise
感谢阅读
欢迎指正、探讨
😀 各位喜欢的看官,欢迎 star 🌟
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。