一、前提了解
关于promise:
首先,我们都知道它有三个状态,可以进行异步操作,并且有错误的处理机制。除此之外,它是可以链式调用的。常用的几个方法:then、catch、finally,以及all、race、resolve、reject等方法。
接着简单地了解一下它产生的背景。
众所周知,JavaScript是单线程的,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。
回调函数是最简单的一种方式,但是其容易形成回调地狱,即多个回调函数嵌套,降低代码可读性,容易出错。
于是ES6中出现了Promise,它有三种状态,pending、fulfilled、rejected,并且可以链式调用。Promise实际上是利用编程技巧将回调函数的横向加载,改成纵向加载,达到链式调用的效果,避免回调地狱。最大问题是代码冗余,原来的任务被Promise包装了一下,会有很多then,catch。当然这样也会导致代码的复杂性,所以后面又出现了async、await(ES8中被提出来)。
这是其产生的背景和作用,那么接着说我们知道的,promise的构成:
1、三个状态:pending、fulfilled、rejected
2、构造函数:执行器函数executor
3、then、catch、finally、resolve、reject、race、all、allSettle等方法,一般了解前三个就好
二、开始实现
先实现简单的,三个状态的变化,以及基本的then方法。
已知三个状态:
- Pending(待定):初始状态,操作尚未完成。
- Fulfilled(已兑现):操作成功完成,并返回一个值。
- Rejected(已拒绝):操作失败,并返回一个原因。
Promise 的状态一旦从 Pending 转为 Fulfilled 或 Rejected,就不可更改。
因为promise需要异步执行,所以用 queueMicrotask 或者 setTimeout 模拟微任务,主要是为了确保异步回调不会阻塞当前同步代码的执行。而且这里涉及一个知识,Promise.resolve().then是微任务,尽管setTimeout是宏任务,但也能够满足异步需求。
具体代码:
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态
this.value = undefined; // 成功时的值
this.reason = undefined; // 失败时的原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach((cb) => cb()); // 执行所有成功回调
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach((cb) => cb()); // 执行所有失败回调
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(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') {
// 保证异步执行
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
}
});
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; })
);
}
// 静态 resolve 方法
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise((resolve) => resolve(value));
}
// 静态 reject 方法
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
// 帮助处理 then 返回值
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (err) {
if (called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
module.exports = MyPromise;
测试:
// 测试
const promise = new MyPromise((resolve) => {
setTimeout(() => resolve('Success!'), 1000);
});
promise
.then((value) => {
console.log('First:', value);
return 'Next Step';
})
.then((value) => {
console.log('Second:', value);
return new MyPromise((resolve) => {
setTimeout(() => resolve('Final Step'), 1000);
});
})
.then((value) => console.log('Third:', value));
三、关于then的处理更简单的写法
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const result = typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value;
resolve(result);
} catch (error) {
reject(error);
}
};
const handleRejected = () => {
try {
const result = typeof onRejected === 'function' ? onRejected(this.reason) : this.reason;
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
setTimeout(handleFulfilled, 0);
} else if (this.state === 'rejected') {
setTimeout(handleRejected, 0);
} else {
this.fulfillQueue.push(() => setTimeout(handleFulfilled, 0));
this.rejectQueue.push(() => setTimeout(handleRejected, 0));
}
});
}
四、finally:
finally(callback) {
return this.then(
(value) => MyPromise.resolve(callback()).then(() => value),
(reason) => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
五、all
我们都知道,Promise.all的主要功能是接收一个可迭代对象(通常是数组),其中包含多个Promise或非 Promise值,返回一个新的Promise,然后其运行逻辑就是:
- 如果所有 Promise 都成功,新的 Promise 会被 resolve,返回值是一个包含所有结果的数组。
- 如果其中任意一个 Promise 被 reject,新的 Promise 会立即被 reject,返回原因。
然后处理过程需要对传入的值是否是数组,是否是promise等进行判断,然后统计fulfillCount :
Promise.all = function (iterable) {
// 将传入的可迭代对象转化为数组
var tasks = Array.from(iterable);
// 如果任务数组为空,直接返回一个 resolve 的 Promise,值为空数组
if (tasks.length === 0) {
return Promise.resolve([]);
}
// 如果所有任务都不是 Promise,则直接返回 resolve 的 Promise
if (tasks.every(task => !(task instanceof Promise))) {
return Promise.resolve(tasks);
}
// 返回一个新的 Promise,用于处理多个任务
return new Promise((resolve, reject) => {
var values = new Array(tasks.length).fill(null); // 用于存储任务结果的数组
var fulfillCount = 0; // 用于统计已完成任务的数量
// 遍历任务数组
tasks.forEach((task, index, arr) => {
if (task instanceof Promise) {
// 如果任务是一个 Promise,处理其结果
task.then(value => {
fulfillCount++; // 成功的任务计数+1
values[index] = value; // 将结果存储在正确的索引位置
// 如果所有任务都完成,resolve 最终的结果数组
if (fulfillCount === arr.length) {
resolve(values);
}
}, reason => {
// 如果任意任务失败,直接 reject
reject(reason);
});
} else {
// 如果任务不是 Promise,直接存储结果
fulfillCount++;
values[index] = task;
}
});
});
};
最后再补充
这里有一个基于Promises/A+ 标准实现的例子,很规范,核心思想一致:GitHub - cumt-robin/promises-aplus-robin: a lightweight Promises/A+ implementation that is able to detect circular thenable chain.
主要了解核心,还有finally方法就差不多了。