Promise的源码实现
Opened this issue · 2 comments
xiaotiandada commented
参考文章
- Promise 的源码实现(完美符合 Promise/A+规范)
- 手写一个 Promise/A+,完美通过官方 872 个测试用例
- PromiseA+测试从一个报错解读 promises-aplus-tests 源码
- Promises/A+
- Promise/A+ 规范
- 基于 Promise A+ 规范手写一个 typescript 版的 Promise
// Promises/A+
// https://promisesaplus.com/
type Resolve<T> = (value?: T | PromiseLike<T>) => void;
type Reject = (reason?: any) => void;
type onFinally = (() => void) | undefined | null;
type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void;
enum Status {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected',
}
const isFunction = (value: any): value is Function => typeof value === 'function';
class PromiseCustom<T> {
private status: Status;
private value: T | null;
private reason: any;
private onFulfilledCallbacks: ((value: T) => void)[];
private onRejectedCallbacks: ((reason: any) => void)[];
constructor(executor: Executor<T>) {
this.status = Status.PENDING; // 初始状态为 pending
this.value = null; // 初始化 value
this.reason = null; // 初始化 reason
// 构造函数里面添加两个数组存储成功和失败的回调
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
}
// resolve 方法参数是 value
private resolve = (value?: T | PromiseLike<T>): void => {
if (this.status === Status.PENDING) {
this.status = Status.FULFILLED;
this.value = value as T;
// resolve 里面将所有成功的回调拿出来执行
this.onFulfilledCallbacks.forEach((callback) => {
callback(this.value!);
});
}
};
// reject 方法参数是 reason
private reject = (reason?: any): void => {
if (this.status === Status.PENDING) {
this.status = Status.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => {
callback(this.reason);
});
}
};
public then(onFulfilled?: Resolve<T>, onRejected?: Reject) {
// 如果onFulfilled不是函数,给一个默认函数,返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
// 如果onRejected不是函数,给一个默认函数,抛出reason的Error
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason: any) => {
throw reason;
};
let promiseCustomReturn = new PromiseCustom((resolve, reject) => {
if (this.status === Status.FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled!(this.value as T);
resolvePromise(promiseCustomReturn, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.status === Status.REJECTED) {
setTimeout(() => {
try {
let x = onRejected!(this.reason);
resolvePromise(promiseCustomReturn, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.status === Status.PENDING) {
// 如果还是PENDING状态,将回调保存下来
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled!(this.value as T);
resolvePromise(promiseCustomReturn, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected!(this.reason);
resolvePromise(promiseCustomReturn, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
}
});
return promiseCustomReturn;
}
public catch(onRejected: Reject) {
this.then(undefined, onRejected);
}
public finally(onfinally?: onFinally): any {
return this.then(
(value: any) =>
PromiseCustom.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => value),
(reason: any) =>
PromiseCustom.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => {
throw reason;
}),
);
}
static resolve(parameter: any) {
if (parameter instanceof PromiseCustom) {
return parameter;
}
return new PromiseCustom((resolve) => {
resolve(parameter);
});
}
static reject(reason: any) {
return new PromiseCustom((_, reject) => {
reject(reason);
});
}
static all(promiseList: any[]) {
return new PromiseCustom((resolve, reject) => {
if (!Array.isArray(promiseList)) {
return reject(new TypeError('Argument is not iterable'));
}
if (promiseList.length === 0) {
return resolve([]);
}
let result: any[] = [];
promiseList.forEach((promise, index) => {
PromiseCustom.resolve(promise).then(
(value) => {
result[index] = value;
if (index >= promiseList.length) {
resolve(result);
}
},
(reason) => {
reject(reason);
},
);
});
});
}
static race(promiseList: any[]) {
return new PromiseCustom((resolve, reject) => {
if (!Array.isArray(promiseList)) {
return reject(new TypeError('Argument is not iterable'));
}
if (promiseList.length === 0) {
return resolve();
} else {
for (let i = 0; i < promiseList.length; i++) {
PromiseCustom.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
},
);
}
}
});
}
static allSettled(promiseList: any[]) {
return new PromiseCustom((resolve, reject) => {
if (!Array.isArray(promiseList)) {
return reject(new TypeError('Argument is not iterable'));
}
let result: any[] = [];
if (promiseList.length === 0) {
return resolve(result);
} else {
for (let i = 0; i < promiseList.length; i++) {
PromiseCustom.resolve(promiseList[i]).then(
(value) => {
result[i] = {
status: Status.FULFILLED,
value: value,
};
if (i >= promiseList.length - 1) {
return resolve(result);
}
},
(reason) => {
result[i] = {
status: Status.REJECTED,
reason: reason,
};
if (i >= promiseList.length - 1) {
return resolve(result);
}
return resolve(result);
},
);
}
}
});
}
static any(promiseList: any[]) {
let resultError: any = [];
return new PromiseCustom((resolve, reject) => {
if (!Array.isArray(promiseList)) {
return reject?.(new TypeError('Argument is not iterable'));
}
if (promiseList.length === 0) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
return reject(new TypeError('All promises were rejected'));
} else {
promiseList.forEach((promise, index) => {
PromiseCustom.resolve(promise).then(
(value) => {
resolve(value);
},
(reason) => {
resultError.push(reason);
if (index >= promiseList.length) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
reject(new TypeError(resultError));
}
},
);
});
}
});
}
// promises-aplus-tests
// adapter.deferred
static deferred() {
const result: any = {};
result.promise = new PromiseCustom((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
}
function resolvePromise<T>(promise: PromiseCustom<T>, x: any, resolve: Resolve<T>, reject: Reject) {
if (promise === x) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof PromiseCustom) {
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
x.then((y: any) => {
resolvePromise(promise, y, resolve, reject);
}, reject);
} else if ((x && typeof x === 'object') || typeof x === 'function') {
let called = false;
try {
let then = x.then;
if (typeof then === 'function') {
// 4) 2.3.3: Otherwise, if `x` is an object or function, 2.3.3.3: If `then` is a function, call it with `x` as `this`, first argument `resolvePromise`, and second argument `rejectPromise` 2.3.3.3.1: If/when `resolvePromise` is called with value `y`, run `[[Resolve]](promise, y)` `y` is not a thenable `y` is `null` `then` calls `resolvePromise` asynchronously via return from a rejected promise:
then.call(
x,
(y: T) => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r: any) => {
if (called) return;
called = true;
reject(r);
},
);
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
return reject(error);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
// 2.3.4: If `x` is not an object or function, fulfill `promise` with `x` The value is `undefined`. The value is `null`
resolve(x);
}
}
export = PromiseCustom;
xiaotiandada commented
测试用例
"use strict";
let PromiseCustom = require("./PromiseCustom");
{
const promiseA = new PromiseCustom((resolve, reject) => {
resolve(777);
});
// At this point, "promiseA" is already settled.
promiseA.then((val) => console.log("asynchronous logging has val:", val));
console.log("immediate logging");
// produces output in this order:
// immediate logging
// asynchronous logging has val: 777
}
// ---------------------------------------------------------------------------------------
{
const promise1 = PromiseCustom.resolve(123);
promise1.then((value) => {
console.log(value);
// Expected output: 123
});
// ---------------------------------------------------------------------------------------
function resolved(result) {
console.log('Resolved');
}
function rejected(result) {
console.error(result);
}
Promise.reject(new Error('fail')).then(resolved, rejected);
// Expected output: Error: fail
}
// ---------------------------------------------------------------------------------------
{
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// Expected output: Array [3, 42, "foo"]
}
// ---------------------------------------------------------------------------------------
{
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// Expected output: "two"
}
// ---------------------------------------------------------------------------------------
{
const promise1 = new PromiseCustom((resolve, reject) => {
throw new Error('Uh-oh!');
});
promise1.catch((error) => {
console.error(error);
});
// Expected output: Error: Uh-oh!
}
// ---------------------------------------------------------------------------------------
{
new PromiseCustom()
.finally(() => {
console.log('Experiment completed');
});
}
// ---------------------------------------------------------------------------------------
{
const promise1 = PromiseCustom.resolve(3);
const promise2 = new PromiseCustom((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// Expected output:
// "fulfilled"
// "rejected"
}
// ---------------------------------------------------------------------------------------
{
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2, promise3];
Promise.any(promises).then((value) => console.log(value));
// Expected output: "quick"
}
xiaotiandada commented
function executePromise(promises) {
if (!Array.isArray(promises)) {
return Promise.reject(new TypeError('Arguments is not iterable'));
}
const result = [];
let len = promises.length;
if (!len) {
return Promise.resolve(result);
}
function executeNextPromise(index) {
if (index >= len) {
return Promise.resolve(result);
}
return promises[index]().then(
(value) => {
result[index] = {
status: 'fulfilled',
reason: value,
};
return executeNextPromise(index + 1);
},
(error) => {
result[i] = {
status: 'rejected',
reason: error,
};
return executeNextPromise(index + 1);
},
);
}
return executeNextPromise(0);
}
// 示例用法
const promise1 = () =>
new Promise((resolve) =>
setTimeout(() => {
console.log(1);
resolve(1);
}, 200),
);
const promise2 = () =>
new Promise((resolve) =>
setTimeout(() => {
console.log(2);
resolve(2);
}, 100),
);
const promise3 = () =>
new Promise((resolve) =>
setTimeout(() => {
console.log(3);
resolve(3);
}, 200),
);
const promises = [promise1, promise2, promise3];
executePromise(promises)
.then((result) => {
console.log('done');
console.log(result); // 输出 [1, 2, 3]
})
.catch((error) => {
console.error(error);
});