Promise:一场关于异步的优雅冒险
嗨,各位探险家们!今天我们来聊聊 Promise,这玩意儿就像异步世界里的罗盘,能指引我们穿越回调地狱,最终到达优雅编程的彼岸。
想象一下,你要去一个遥远的国度,交通工具是飞鸽传书。你写好信,绑在鸽子腿上,然后就只能等着,不知道鸽子啥时候到,也不知道那边回信啥时候来。这就是异步操作,而 Promise 就是帮你管理这些鸽子的“信鸽调度中心”。
什么是 Promise?
Promise 本质上是一个对象,代表一个异步操作的最终完成 (或失败) 及其结果值。它有三种状态:
- Pending (等待中): 鸽子还没飞到,或者对方还没回信。这是 Promise 的初始状态。
- Fulfilled (已成功): 鸽子安全抵达,并且带回了成功的消息。Promise 已经完成,并且有一个结果值。
- Rejected (已失败): 鸽子迷路了,或者被老鹰叼走了,或者对方回信说“滚”。Promise 已经失败,并且有一个失败原因。
Promise/A+ 规范:Promise 的“交通规则”
Promise 很好用,但如果每个人都用自己的方式“养鸽子”,那异步世界就乱套了。所以,Promise/A+ 规范就诞生了,它定义了 Promise 应该如何工作,就像交通规则一样,保证大家的行为一致。
我们今天的目标,就是用 JavaScript 实现一个符合 Promise/A+ 规范的“信鸽调度中心”。
实现一个简单的 Promise
首先,我们定义一个 MyPromise
类:
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态
this.value = undefined; // 成功的值
this.reason = undefined; // 失败的原因
this.onResolvedCallbacks = []; // 成功的回调函数队列
this.onRejectedCallbacks = []; // 失败的回调函数队列
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn()); // 执行成功的回调函数
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn()); // 执行失败的回调函数
}
};
try {
executor(resolve, reject); // 立即执行 executor 函数
} catch (error) {
reject(error); // 如果 executor 抛出错误,则 reject
}
}
then(onFulfilled, onRejected) {
// 处理 onFulfilled 和 onRejected 为函数,如果不是函数,则忽略
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 异步执行,保证在 promise2 构造函数执行完之后再执行
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
// 异步执行,保证在 promise2 构造函数执行完之后再执行
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
// 如果状态还是 pending,说明异步操作还没有完成,先把回调函数保存起来
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
);
}
static resolve(value) {
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
static all(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const result = [];
let count = 0;
if (promises.length === 0) {
return resolve(result);
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
MyPromise.resolve(promise).then(value => { // 确保是 Promise 对象
result[i] = value;
count++;
if (count === promises.length) {
resolve(result);
}
}, reason => {
reject(reason);
});
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
MyPromise.resolve(promise).then(value => {
resolve(value);
}, reason => {
reject(reason);
});
}
});
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 避免循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
}
let called = false; // 避免多次调用 resolve 或 reject
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
const then = x.then; // 尝试获取 then 方法
if (typeof then === 'function') {
// 如果 x 是一个 Promise,则递归解析
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject); // 递归解析 y
}, (r) => {
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x 不是一个 Promise,则直接 resolve
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
// 如果 x 是一个普通值,则直接 resolve
resolve(x);
}
}
// 为了方便测试,我们可以将 MyPromise 暴露出去
MyPromise.deferred = function() {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = MyPromise;
让我们逐步分解这段代码:
-
constructor(executor)
: 这是 Promise 的构造函数。executor
是一个函数,它接收两个参数:resolve
和reject
。resolve
用于将 Promise 状态变为fulfilled
,reject
用于将 Promise 状态变为rejected
。 -
state
、value
、reason
: 这三个属性分别用于存储 Promise 的状态、成功的值和失败的原因。 -
onResolvedCallbacks
、onRejectedCallbacks
: 这两个数组用于存储成功和失败的回调函数。当 Promise 的状态变为fulfilled
或rejected
时,我们会依次执行这些回调函数。 -
resolve(value)
: 这个函数用于将 Promise 的状态变为fulfilled
,并将成功的值存储到value
属性中。它还会遍历onResolvedCallbacks
数组,依次执行其中的回调函数。 -
reject(reason)
: 这个函数用于将 Promise 的状态变为rejected
,并将失败的原因存储到reason
属性中。它还会遍历onRejectedCallbacks
数组,依次执行其中的回调函数。 -
then(onFulfilled, onRejected)
: 这是 Promise 最重要的一个方法。它接收两个参数:onFulfilled
和onRejected
,分别用于处理 Promise 成功和失败的情况。它会返回一个新的 Promise 对象,这个新的 Promise 对象的 resolve 或 reject 取决于onFulfilled
和onRejected
的返回值。 -
resolvePromise(promise2, x, resolve, reject)
: 这个函数用于处理then
方法返回的 Promise 对象。它会判断x
的类型,如果x
是一个 Promise 对象,则递归调用resolvePromise
函数,直到x
不是一个 Promise 对象为止。如果x
是一个普通值,则直接 resolvepromise2
。 -
catch(onRejected)
: 相当于then(null, onRejected)
,用于捕获 Promise 的错误。 -
finally(callback)
: 无论 Promise 成功还是失败,都会执行callback
函数。 -
static resolve(value)
: 用于创建一个状态为fulfilled
的 Promise 对象。 -
static reject(reason)
: 用于创建一个状态为rejected
的 Promise 对象。 -
static all(promises)
: 接收一个 Promise 数组,当所有 Promise 都成功时,返回一个包含所有 Promise 结果的数组。如果其中一个 Promise 失败,则返回一个 rejected 的 Promise。 -
static race(promises)
: 接收一个 Promise 数组,返回第一个 resolve 或 reject 的 Promise。
关键点和难点
-
异步执行:
resolve
和reject
函数需要在异步操作完成后才能执行。为了保证在 Promise 构造函数执行完之后再执行回调函数,我们需要使用setTimeout
或process.nextTick
等方法将回调函数放入事件循环队列中。 -
resolvePromise
函数: 这是实现 Promise/A+ 规范的关键。它需要处理各种情况,包括:x
是一个 Promise 对象x
是一个 thenable 对象x
是一个普通值- 循环引用
-
状态的不可逆性: Promise 的状态一旦变为
fulfilled
或rejected
,就不能再改变。
Promise/A+ 规范验证
为了验证我们的 MyPromise
是否符合 Promise/A+ 规范,我们可以使用 promises-aplus-tests 这个测试工具。
-
首先,安装
promises-aplus-tests
:npm install -g promises-aplus-tests
-
然后,创建一个测试文件
test.js
,内容如下:const MyPromise = require('./MyPromise'); MyPromise.deferred = MyPromise.deferred || function () { let resolve, reject; let promise = new MyPromise(function (_resolve, _reject) { resolve = _resolve; reject = _reject; }); return { promise: promise, resolve: resolve, reject: reject }; }; module.exports = MyPromise;
-
最后,运行测试:
promises-aplus-tests test.js
如果所有的测试都通过了,那么恭喜你,你的 MyPromise
已经符合 Promise/A+ 规范了!
总结
我们用 JavaScript 实现了一个简单的 Promise,并且验证了它符合 Promise/A+ 规范。虽然这只是一个简单的实现,但它包含了 Promise 的核心概念和原理。
特性 | 描述 |
---|---|
状态 | Pending, Fulfilled, Rejected |
then 方法 |
接收 onFulfilled 和 onRejected 回调函数,返回一个新的 Promise |
resolvePromise |
处理 then 方法返回的 Promise 对象,递归解析 Promise |
静态方法 | resolve , reject , all , race |
错误处理 | 使用 try...catch 捕获 executor 函数和回调函数中的错误 |
异步执行 | 使用 setTimeout 或 process.nextTick 将回调函数放入事件循环队列中,保证在 Promise 构造函数执行完之后再执行回调函数 |
通过这次“信鸽调度中心”的冒险,相信你对 Promise 有了更深入的理解。希望你能将 Promise 应用到你的实际项目中,让异步编程变得更加优雅和高效!
现在,拿起你的罗盘,开始你的异步编程之旅吧!