前言
Promise是什么
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
Promise A+规范
上面是Promise的一个通俗意义上的解释,也是我们在学习和使用时最先接触到的。其实在这背后,有一个开放、健全且通用的 JavaScript Promise 标准— Promise A+ (promisesaplus.com/)规范 来详尽的描述,我们使用的ES6 Promise 对象就是基于此规范实现的,并且扩展了一些方法。
因此,当你在使用Promise时,如果有一些执行结果无法理解,可以从规范中去看经历了什么样的过程。而手写一个Promise可以帮助我们更加深刻的理解Promise并使用
实现
关于规范的具体内容,请看上面官网,或这个翻译文档, 在此不过多赘述和解读,在具体实现过程中会有说明
构造Promise
let promise new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
})
Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是
then方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。本规范详细列出了
then方法的执行过程,所有遵循 Promises/A+ 规范实现的 promise 均可以本标准作为参照基础来实施then方法。
从上面可以看到,ES6的Promise在使用时通过 new 方法生成了一个Promise对象,传的是一个参数,即用户要执行的函数,函数中传了两个参数,用来在用户操作完成后更新promise状态, resolve 更新promise状态为 fulfilled , reject 更新promise状态为 rejected 。而在规范中,并没有对构造这一部分的说明。因此我们可以参照上面的例子实现构造函数
// 定义状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
// 定义判断方法
function isFn(fn) {
return typeof fn === "function";
}
function isObj(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// 自定义Promise类
class MyPromise {
constructor(executor) {
// 传入执行函数
this._state = PENDING; // 状态
this._value = undefined; // 终值
this._reason = undefined; // 据因
executor(this._resolve.bind(this), this._reject.bind(this));
}
_resolve(value) {
// 可以迁移至执行态或拒绝态
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
}
}
_reject(reason) {
// 可以迁移至执行态或拒绝态
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
}
}
}
定义then方法
let promise new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
})
promise.then(
(value) => {
console.log(value);
},
(e) => {
console.error(e);
}
)
一个 promise 必须提供一个
then方法以访问其当前值、终值和据因。promise 的then方法接受两个参数:promise.then(onFulfilled, onRejected)
构造promise, 更新了promise的状态后。根据规范,这个时候要调用用户注册的回调函数,而回调函数是通过then方法传入的。因此可以如下实现then方法
class MyPromise {
constructor(executor) {
// ...
// 支持then多次调用,因此用数组存储每次调用的回调
this._onResolvedCallbacks = [];
this._onRejectedCallbacks = [];
// ...
}
then(onResolved, onRejected) {
// 如果当前是pending状态,将回调存入数组
if (this._state === PENDING) {
this._onResolvedCallbacks.push(onResolved);
this._onRejectedCallbacks.push(onRejected);
}
// 当promise已经被决议了(非pending态),注册的回调也需要执行
if (this._state === FULFILLED) {
setTimeout(() => {
isFn(onResolved) && onResolved(this._value)
})
}
if (this._state === REJECTED) {
setTimeout(() => {
isFn(onRejected) && onRejected(this._reason)
})
}
}
_resolve(value) {
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
// onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
// 即回调必须在状态改变后的下一轮循环中调用
setTimeout(() => {
this._onResolvedCallbacks.forEach((callback) => {
// 如果 onFulfilled 不是函数,其必须被忽略
isFn(callback) && callback(value)
})
})
}
}
_reject(reason) {
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
setTimeout(() => {
this._onRejectedCallbacks.forEach((callback) => {
isFn(callback) && callback(reason)
})
})
}
}
}
上面实现了规范里面then函数的好几条规范,具体可见注释。通过这个基本的实现,即可使用promise一个基本的功能:一旦promise状态改变,会调用then注册的所有的回调函数
链式调用
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
}).then((value) => {
console.log(value);
return new Promise((resolve, reject) => {
setTimeout() => {
resolve(2)
})
})
}).then((value) => {
console.log(value)
})
then 方法必须返回一个 promise 对象 [注3][3]:
promise2 = promise1.then(onFulfilled, onRejected);
Promise的链式调用本质是then方法返回了一个promise对象。在上面并未实现,可以通过在then最后返回当前的promise this 简单实现,它可以满足then中不做任何值的修改的链式回调。但它不能满足then中任何修改的链式回调,因此违背了 必须拥有一个不可变的终值 和 必须拥有一个不可变的据因 的规范。因此需要构造一个新的promise对象,这个对象的构造规则可以参考 Then → 返回 和 Promise解决过程 章节
class MyPromise {
// ...
then(onResolved, onRejected) {
// PENDING状态
if (this._state === PENDING) {
// 返回promise2
let promise2 = new MyPromise((resolve, reject) => {
// 保存成功回调
this._onResolvedCallbacks.push(() => {
this._executor(onResolved, this._value, promise2, resolve, reject);
});
// 保存拒绝回调
this._onRejectedCallbacks.push(() => {
this._executor(onRejected, this._reason, promise2, resolve, reject, REJECTED);
});
});
return promise2;
}
// FULFILLED状态
if (this._state === FULFILLED) {
let promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
this._executor(onResolved, this._value, promise2, resolve, reject);
});
});
return promise2;
}
// PREJECTED状态
if (this._state === REJECTED) {
let promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
this._executor(onRejected, this._reason, promise2, resolve, reject, REJECTED);
});
});
return promise2;
}
}
// 新promise内部的执行函数
_executor(handler, data, promise2, resolve, reject, type = FULFILLED) {
// 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
// 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
if (!isFn(handler)) {
type === FULFILLED ? resolve(data) : reject(data);
} else {
let value;
try {
value = handler(data);
} catch (e) {
// 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
return reject(e);
}
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
this._resolution(promise2, value, resolve, reject);
}
}
// Promise 解决过程
_resolution(promise, x, resolve, reject) {
if (x === promise) {
// x 与 promise相等
reject(new TypeError("Chaining cycle detected for myPromise #<MyPromise>"));
} else if (x instanceof MyPromise) {
// x 为 Promise
// 如果 x 为 Promise ,则使 promise 接受 x 的状态 注4:
// 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
// 如果 x 处于执行态,用相同的值执行 promise
// 如果 x 处于拒绝态,用相同的据因拒绝 promise
x.then((y) => {
this._resolution(promise, y, resolve, reject);
}, reject);
} else if (isObj(x) || isFn(x)) {
// x 为对象或函数
let then;
try {
then = x.then;
} catch (e) {
return reject(e);
}
if (isFn(then)) {
// 如果 then 是函数,将 x 作为函数的作用域 this 调用之
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
let called = false;
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
(y) => {
if (!called) {
called = true;
this._resolution(promise, y, resolve, reject);
}
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (!called) {
called = true;
reject(r);
}
}
);
} catch (e) {
if (!called) {
return reject(e);
}
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
// ...
}
在返回promise里,根据要求我们构建了一个新的promise对象。在新的promise的执行函数里面,根据当前promise状态保存或执行了一个 _executor 方法,该方法即 Then->返回 里的规范实现。主要处理了非 Promise解决过程 case下的promise状态和值传递。而 _resolution 方法是 Promise解决过程 里规范实现,处理了该case下的promise状态和值传递
异常传递
通过规范,可以看到,promise决议后出现的异常,如回调 onFulFilled onRejected 当中出现的异常, Promise解决过程 出现的异常,都会被返回的 promise reject 。这是因为 promise决议后就不能改变状态等了,而新的返回的promise当中可以保存下来被捕获等。这样形成了一个异常的链式传递。
以上,就是Promise A+ 规范的一个实现过程
测试
完成Promise A+ 规范后,可以使用Github的一个包promises-tests来测试是否符合。
也可以参考我的实现版本 看下效果
ES6版
ES6版本的版本除了对规范的实现外,还补充和扩展的了一些功能。感兴趣的可以去实现这些功能,加深理解和使用
-
Promise.resolve, Promise.reject, Promise.all, Promise.race Promise.prototype.catch等方法
-
当异常到最外层时,除了会传递给返回的promise,还会控制台打印异常,可以通过在
reject中判断是否打印_reject(reason) { if (this._onRejectedCallbacks.length === 0) { console.error(this._reason); } } -
当promise
resolve一个Promise或thenable对象时,会在回调中异步的展开,而不是立即执行回调函数。可以在resolve中加入此逻辑_resolve(value) { // 当resolve promise时,传递展开 if (value instanceof MyPromise) { value.then(this._resolve.bind(this), this._reject.bind(this)); return; } }