手写 promise
心血来潮想手写一下 promise, 浅浅用 js 试一下.
什么是 promise
这部分可以查看 promise a+规范内容, 英文, 非官方中文翻译
也可以想一下平时是怎么使用 promise 的
console.log('start');
const p = new Promise((resolve, reject) => {
console.log('promise');
resolve(1);
});
console.log('end');
Promise 的构造函数需要一个函数, 这个函数接受两个函数作为参数, resolve 会将这个 Promise 标记成完成态, 并设置值; reject 会标记成拒绝态, 并设置原因. 可以写出下面这个东西.
初步实现 promise
执行上面的代码, 应该会从上往下打印出来. 说明传进去的函数是同步执行的. 可以写出这样的类.
const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
class MyPromise {
#state;
#value;
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
}
constructor(executor) {
const resolve = (val) => {
this.#setState(FULFILLED, val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
还有一个.then 方法. .then 方法接受两个函数, 一个是完成时执行, 一个是拒绝时执行, .then 本身会返回一个 promise .
这个 then 返回的 promise(记作 p1)在 promise a+规范里有要求:
- 如果 onFulfilled 或者 onRejected 返回一个值 x, 那 p1 应该 resolve 这个 x
- 如果 onFulfilled 或者 onRejected 抛出一个异常 err, 那 p1 应该 reject 这个 err
const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
class MyPromise {
#state;
#value;
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
}
constructor(executor) {
// 初始为pending状态
this.#state = PENDING;
const resolve = (val) => {
this.#setState(FULFILLED, val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const callback =
this.#state === FULFILLED ? onFulfilled : onRejected;
try {
const res = callback(this.#value);
resolve(res);
} catch (error) {
reject(error);
}
});
}
}
可以简单测试一下现在的 MyPromise, 应该有一点简单的功能了.
const p = new MyPromise((resolve, reject) => {
resolve(1); // 也可以试一下reject
});
p.then(
(res) => {
console.log(1);
},
(err) => {
console.log('err', err);
}
);
如果是 resolve 的话应该会打印出 1, reject 会打印出 err 1. 可以试试把 MyPromise 改成 Promise, 应该是一样的.
不过其实有点问题, 这里的 executor 是同步的, 如果是异步的就不行了.
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 500);
});
p.then(
(res) => {
console.log(1);
},
(err) => {
console.log('err', err);
}
);
会发现打印出了err undefined
, 这是因为执行.then
的时候, 状态还是 pending
, #value
是 undefined
, 直接进入了 onRejected
里了.
可以把 callback 执行的时机改一下, 改在 #setState
里执行. 只需要先将函数存起来就可以了.
// 这三行不写了
class MyPromise {
#state;
#value;
#handle; // 用来存要执行的函数
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
this.#handle();
}
constructor(executor) {
// 初始为pending状态
this.#state = PENDING;
this.#handle = () => {};
const resolve = (val) => {
this.#setState(FULFILLED, val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handle = () => {
const callback =
this.#state === FULFILLED ? onFulfilled : onRejected;
try {
const res = callback(this.#value);
resolve(res);
} catch (error) {
reject(error);
}
};
// 如果已经完成了, 就立刻执行
if (this.#state !== PENDING) {
this.#handle();
}
});
}
}
这样的话, 再执行一次应该就可以正确的打印出 1 了.
不知道大家有没有对一个 promise 用多个.then, 我也是看资料才发现, 平时没用过.
const p = new MyPromise((resolve, reject) => {
resolve(1);
});
p.then(
(res) => {
console.log('第一个then', 1);
},
(err) => {
console.log('err', err);
}
);
p.then(
(res) => {
console.log('第二个then', 1);
},
(err) => {
console.log('err', err);
}
);
执行上面的代码, 会发现只打印出第二个 then 1
, 但是如果使用 Promise
的话应该两个都会打印. 这是因为第二个 then 的时候把#handle
覆盖掉了, 应该用一个数组来存回调函数.
class MyPromise {
#state;
#value;
#handlers = []; // 存在一个数组里
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
this.#runTask();
}
// 执行任务
#runTask() {
for (const handler of this.#handlers) {
handler();
}
this.#handlers = [];
}
constructor(executor) {
// 初始为pending状态
this.#state = PENDING;
this.#handlers = [];
const resolve = (val) => {
this.#setState(FULFILLED, val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlers.push(() => {
const callback =
this.#state === FULFILLED ? onFulfilled : onRejected;
try {
const res = callback(this.#value);
resolve(res);
} catch (error) {
reject(error);
}
});
// 如果已经完成了, 就立刻执行
if (this.#state !== PENDING) {
this.#runTask();
}
});
}
}
这里把 handler 的执行写在一个函数里了. 现在再运行一次应该就两个都能打印了.
异步执行任务
可以执行下面的代码
const p = new MyPromise((resolve, reject) => {
resolve(1);
});
p.then(
(res) => {
console.log(1);
},
(err) => {
console.log('err', err);
}
);
console.log('end');
应该会发现先打印了 1, 然后打印 end. 但是如果换成 Promise 的话, 应该会先打印 end, 再打印 1.
js 有一个任务队列的机制, 它有微队列(一般 promise
会放在这)和一些其它队列( setTimeout
, setInterval
之类的) , 在同步代码执行完毕后, js 会从任务队列里取任务, 微队列优先级最高.
在这里应该要把 handlers
里面的函数放在微队列里面执行.
#runTask() {
queueMicrotask(()=>{
for (const handler of this.#handlers) {
handler();
}
this.#handlers = [];
})
}
有一点点兼容性问题, 如果要做的完美的话, 可以这样写:
function runMicroTask(fn) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn);
} else if (
typeof process === 'object' &&
typeof process?.nextTick === 'function'
) {
process.nextTick(fn);
} else if (typeof MutationObserver === 'function') {
const text = document.createTextNode('');
const observer = new MutationObserver(fn);
observer.observe(text, {
characterData: true,
});
text.data = '1';
} else {
setTimeout(fn);
}
}
这样的话就尽可能地包括了大部分场景了.
再次执行代码, 应该会先打印出 end, 再打印出 1.
.then 的一些细节
onFulfilled 和 onRejected 不是函数的情况
可以试着执行下面的代码
const p = new MyPromise((resolve, reject) => {
resolve(1);
});
p.then().then((res) => {
console.log(res);
});
const p1 = new MyPromise((resolve, reject) => {
reject(2);
});
p1.then().then(undefined, (err) => {
console.log(err);
});
没意外的话会报错 callback is not a function. 如果换成 Promise
的话应该会正确打印出来. 这是因为现在还没有处理 onFulfilled
或者 onRejected
没传或者不为函数的情况. 下面 promise a+规范说的
记一开始的 promise 为 p, .then 返回的 promise 是 p1
如果 onFulfilled 不是函数(没有传入或传的不是函数), 且 p 已经完成, 那 p1 应该 resolve p 的值
如果 onRejected 不是函数, 且 p 拒绝了, 那 p1 应该 reject p 的值
修改后:
then(onFulfilled, onReject) {
return new MyPromise((resolve, reject) => {
this.#handlers.push(() => {
const callback =
this.#state === FULFILLED ? onFulfilled : onReject;
try {
// 判断callback是不是函数
if(typeof callback === 'function') {
const res = callback(this.#value);
resolve(res);
} else {
// 不是就跟原本的promise保持相同状态, 并给出相同的值
this.#state === FULFILLED ? resolve(this.#value) : reject(this.#value)
}
} catch (error) {
reject(error);
}
});
// 如果已经完成了, 就立刻执行
if (this.#state !== PENDING) {
this.#runTask();
}
});
}
现在再执行代码的话, 应该能正确打印.
promise 的处理
继续阅读规范的话, 会发现, 如果 resolve 的东西 x 是一个 promise 或者像 promise 的东西(thenable), 就会采用它的状态和值.
这个 thenable 是一个有 then 方法的对象或者函数, 这样子就算返回自定义的 promise 还是 Promise 都有一个统一的行为.
- x 为 pending promise 会保持 pending 直到 x 不为 pending
- x 为 fulfilled 使用 x resolve 的值 resolve 出去
- x 为 rejected 使用 x reject 的值 reject 出去
比如下面这个代码
const p = new MyPromise((resolve, reject) => {
resolve(
new Promise((resolve, reject) => {
resolve(1);
})
);
});
p.then(console.log);
const p1 = new Promise((resolve, reject) => {
resolve(
new Promise((resolve, reject) => {
resolve(1);
})
);
});
p1.then(console.log);
使用 MyPromise 会打印出 Promise {1} ; Promise 会打印出 1. Promise A+规范要求如果 resolve 的是一个 promise, 应该进行解析
// 类外
function isPromiseLike(obj) {
return typeof obj?.then === 'function';
}
// 类内
#resolvePromise(x) {
if (x === this) {
this.#setState(REJECTED, new Error('TypeError'));
return;
}
if (x instanceof Promise) {
x.then(
(res) => {
this.#setState(FULFILLED, res);
},
(err) => {
this.#setState(REJECTED, err);
}
);
return;
}
if (isPromiseLike(x)) {
// 这一部分基本照着规范写的, 规范里判断了then是不是函数, 我这里isPromiseLike已经判断过就不写了.
let hasCalled = false;
try {
x.then(
(y) => {
if (hasCalled) return;
this.#resolvePromise(y);
hasCalled = true;
},
(r) => {
if (hasCalled) return;
this.#setState(REJECTED, r);
hasCalled = true;
}
);
} catch (error) {
if (hasCalled) return;
this.#setState(REJECTED, error);
}
return;
}
this.#setState(FULFILLED, x);
}
再执行一次代码, 这时候应该都是打印出 1 了.
.catch 方法和.finally 方法
这两个方法 promise a+里没有给出, 可以查看 MDN 的文档, 在这里
.catch
文档里说等效于.then(undefined, onRejected)
catch(onReject) {
return this.then(undefined, onReject);
}
.finally
文档里说.finally(onFinally)
跟.then(onFinally, onFinally)
类似, 不过 onFinally 不接收任何参数, finally 也不会改变原始 promise 的状态. 比如说
const p = new Promise((resolve, reject) => {
// reject(1)
resolve(1);
})
.then(
() => 2,
() => 2
)
.then(console.log);
const p1 = new Promise((resolve, reject) => {
resolve(1);
})
.finally(() => 2)
.then(console.log);
上面那个会打印出 2, 下面那个会打印出 1.
finally(onFinally) {
return this.then(
(res) => {
onFinally();
return res;
},
(err) => {
onFinally();
throw err;
}
);
}
到这里 MyPromise 应该已经完成 Promise A+的大部分要求了, 我也不太清楚.
目前为止的完整代码
目前为止的完整 MyPromise
const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
function runMicroTask(fn) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn);
} else if (
typeof process === 'object' &&
typeof process?.nextTick === 'function'
) {
process.nextTick(fn);
} else if (typeof MutationObserver === 'function') {
const text = document.createTextNode('');
const observer = new MutationObserver(fn);
observer.observe(text, {
characterData: true,
});
text.data = '1';
} else {
setTimeout(fn);
}
}
function isPromiseLike(obj) {
return typeof obj?.then === 'function';
}
class MyPromise {
#state;
#value;
#handlers = [];
constructor(executor) {
// 初始为pending状态
this.#state = PENDING;
this.#handlers = [];
const resolve = (val) => {
this.#resolvePromise(val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
#resolvePromise(x) {
if (x === this) {
this.#setState(REJECTED, new Error('TypeError'));
return;
}
if (x instanceof Promise) {
x.then(
(res) => {
this.#setState(FULFILLED, res);
},
(err) => {
this.#setState(REJECTED, err);
}
);
return;
}
if (isPromiseLike(x)) {
let hasCalled = false;
try {
x.then(
(y) => {
if (hasCalled) return;
this.#resolvePromise(y);
hasCalled = true;
},
(r) => {
if (hasCalled) return;
this.#setState(REJECTED, r);
hasCalled = true;
}
);
} catch (error) {
if (hasCalled) return;
this.#setState(REJECTED, error);
}
return;
}
this.#setState(FULFILLED, x);
}
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
this.#runTask();
}
#runTask() {
runMicroTask(() => {
for (const handler of this.#handlers) {
handler();
}
this.#handlers = [];
});
}
then(onFulfilled, onReject) {
return new MyPromise((resolve, reject) => {
this.#handlers.push(() => {
const callback =
this.#state === FULFILLED ? onFulfilled : onReject;
try {
if (typeof callback === 'function') {
const res = callback(this.#value);
resolve(res);
} else {
this.#state === FULFILLED
? resolve(this.#value)
: reject(this.#value);
}
} catch (error) {
reject(error);
}
});
// 如果已经完成了, 就立刻执行
if (this.#state !== PENDING) {
this.#runTask();
}
});
}
catch(onReject) {
return this.then(undefined, onReject);
}
finally(onFinally) {
return this.then(
(res) => {
onFinally();
return res;
},
(err) => {
onFinally();
throw err;
}
);
}
}
静态方法
resolve
查阅 MDN, 有这样的话
- 如果参数 x 是 Promise, 那直接返回
- 如果参数 x 是 thenable, 那调用 then 函数和两个回调函数
- 如果都不是, 那返回一个 resolve x 的 Promise
static resolve(x) {
if (x instanceof Promise) {
return x;
}
if (isPromiseLike(x)) {
return new MyPromise((resolve, reject) => {
x.then(resolve, reject);
});
}
return new MyPromise((resolve, reject) => {
resolve(x);
});
}
reject
返回一个已被拒绝的对象
static reject(x) {
return new MyPromise((resolve, reject) => {
reject(x);
});
}
try
接受一个回调函数和任意个参数
static try(fn, ...args) {
return new MyPromise((resolve, reject) => {
try {
resolve(fn(...args));
} catch (err) {
reject(err);
}
});
}
all
接受一个 Promise 可迭代对象, 返回一个 Promise, 记作 result. 当所有 Promise 完成时, result 才完成, resolve 其它 Promise resolve 出来的值数组. 有一个失败直接 reject.
先判断一下可迭代对象
function isIterable(obj) {
return (
obj !== null &&
obj !== undefined &&
typeof obj[Symbol.iterator] === 'function'
);
}
下面是 all
static all(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p).then((res) => {
result[index] = res;
i++;
if (i === arr.length) {
resolve(result);
}
}, reject);
});
});
}
allSettled
跟 all 方法类似, 不同的是即使有一个失败了也会执行全部
static allSettled(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p)
.then(
(res) => {
result[index] = {
value: res,
status: FULFILLED,
};
},
(err) => {
result[index] = {
reason: err,
status: REJECTED,
};
}
)
.finally(() => {
i++;
if (i === arr.length) {
resolve(result);
}
});
});
});
}
any
同样是输入一个 Promise 可迭代对象, 不过这个只要有一个 resolve 了, 直接 resolve; 全部都 reject 的话, reject 一个错误原因数组.
static any(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p).then(resolve, (err) => {
i++;
result[index] = err;
if (i === arr.length) {
reject(result);
}
});
});
});
}
race
接受一个 Promise 可迭代对象, resolve/reject 最快完成或拒绝的 Promise
static race(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
for (const p of promises) {
MyPromise.resolve(p).then(resolve, reject);
}
});
}
withResolvers
static withResolvers() {
let resolve, reject;
const promise = new MyPromise((res, rej)=>{
resolve = res;
reject = rej;
})
return {
promise,
resolve,
reject,
}
}
写到这里, 应该所有的静态方法都实现了. 如果能写好 promise 的话这个应该不难. 我这里的实现跟实际实现应该会有一些甚至很大的区别, 功能可能也许相差不大, 我不太确定.
完整的MyPromise
完整代码
const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
function runMicroTask(fn) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn);
} else if (
typeof process === 'object' &&
typeof process?.nextTick === 'function'
) {
process.nextTick(fn);
} else if (typeof MutationObserver === 'function') {
const text = document.createTextNode('');
const observer = new MutationObserver(fn);
observer.observe(text, {
characterData: true,
});
text.data = '1';
} else {
setTimeout(fn);
}
}
function isPromiseLike(obj) {
return typeof obj?.then === 'function';
}
function isIterable(obj) {
return (
obj !== null &&
obj !== undefined &&
typeof obj[Symbol.iterator] === 'function'
);
}
class MyPromise {
#state;
#value;
#handlers = [];
constructor(executor) {
// 初始为pending状态
this.#state = PENDING;
this.#handlers = [];
const resolve = (val) => {
this.#resolvePromise(val);
};
const reject = (val) => {
this.#setState(REJECTED, val);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
#resolvePromise(x) {
if (x === this) {
this.#setState(REJECTED, new Error('TypeError'));
return;
}
if (x instanceof Promise) {
x.then(
(res) => {
this.#setState(FULFILLED, res);
},
(err) => {
this.#setState(REJECTED, err);
}
);
return;
}
if (isPromiseLike(x)) {
let hasCalled = false;
try {
x.then(
(y) => {
if (hasCalled) return;
this.#resolvePromise(y);
hasCalled = true;
},
(r) => {
if (hasCalled) return;
this.#setState(REJECTED, r);
hasCalled = true;
}
);
} catch (error) {
if (hasCalled) return;
this.#setState(REJECTED, error);
}
return;
}
this.#setState(FULFILLED, x);
}
#setState(state, val) {
// a+规范中要求fulfilled和reject状态不可改变
if (this.#state !== PENDING) return;
this.#state = state;
this.#value = val;
this.#runTask();
}
#runTask() {
runMicroTask(() => {
for (const handler of this.#handlers) {
handler();
}
this.#handlers = [];
});
}
then(onFulfilled, onReject) {
return new MyPromise((resolve, reject) => {
this.#handlers.push(() => {
const callback =
this.#state === FULFILLED ? onFulfilled : onReject;
try {
if (typeof callback === 'function') {
const res = callback(this.#value);
resolve(res);
} else {
this.#state === FULFILLED
? resolve(this.#value)
: reject(this.#value);
}
} catch (error) {
reject(error);
}
});
// 如果已经完成了, 就立刻执行
if (this.#state !== PENDING) {
this.#runTask();
}
});
}
catch(onReject) {
return this.then(undefined, onReject);
}
finally(onFinally) {
return this.then(
(res) => {
onFinally();
return res;
},
(err) => {
onFinally();
throw err;
}
);
}
static resolve(x) {
if (x instanceof MyPromise) {
return x;
}
if (isPromiseLike(x)) {
return new MyPromise((resolve, reject) => {
x.then(resolve, reject);
});
}
return new MyPromise((resolve, reject) => {
resolve(x);
});
}
static reject(x) {
return new MyPromise((resolve, reject) => {
reject(x);
});
}
static try(fn, ...args) {
return new MyPromise((resolve, reject) => {
try {
resolve(fn(...args));
} catch (err) {
reject(err);
}
});
}
static all(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p).then((res) => {
result[index] = res;
i++;
if (i === arr.length) {
resolve(result);
}
}, reject);
});
});
}
static allSettled(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p)
.then(
(res) => {
result[index] = {
value: res,
status: FULFILLED,
};
},
(err) => {
result[index] = {
reason: err,
status: REJECTED,
};
}
)
.finally(() => {
i++;
if (i === arr.length) {
resolve(result);
}
});
});
});
}
static any(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
reject([]);
}
const result = new Array(arr.length);
let i = 0;
arr.forEach((p, index) => {
MyPromise.resolve(p).then(resolve, (err) => {
i++;
result[index] = err;
if (i === arr.length) {
reject(result);
}
});
});
});
}
static race(promises) {
if (!isIterable(promises)) {
throw new Error('TypeError');
}
return new MyPromise((resolve, reject) => {
for (const p of promises) {
MyPromise.resolve(p).then(resolve, reject);
}
});
}
static withResolvers() {
let resolve, reject;
const promise = new MyPromise((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
resolve,
reject,
};
}
}
感觉还是有一点意义吧, 至少一段时间内不想再看到 Promise 了. 看着文档写的话应该不会很难写, 如果只看着Promise用法写的话大概会很折磨吧.