Implement the Promises/A+ standard with Next ECMAScript.
Start with handling asynchronous callback, to the implementation with Promises/A+ standard specification
then
: 接收 onFulfilled, onRejected 来注册回调executor
: 提供 resolve, reject 来执行回调
// Handling Callbacks
class Promise {
#callbacks = [];
constructor(executor) {
const resolve = value =>
this.#callbacks.forEach(({ onFulfilled }) => onFulfilled(value));
const reject = reason =>
this.#callbacks.forEach(({ onRejected }) => onRejected(reason));
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
this.#callbacks.push({ onFulfilled, onRejected });
}
}
FAQ: resolve, reject 同步调用,后面的 then 中的回调不会调用
[[PromiseStatus]]
only transition to fulfilled
or rejected
from pending
;
-
pending
- initial state: may transition to either the fulfilled or rejected state
-
resolved
[[PromiseValue]]
must have a value, which must not change.- Asynchronous execution
onFulfilled(value)
callback
-
rejected
[[PromiseReason]]
must have a reason, which must not change.- Asynchronous execution
onRejected(reason)
callback
class Promise {
#callbacks = [];
constructor(executor) {
const settle = (status, value) => {
if (this['[[PromiseStatus]]'] === 'pending') {
this['[[PromiseStatus]]'] = status;
this['[[PromiseValue]]'] = value;
setTimeout(() => {
this.#callbacks.forEach(({ onFulfilled, onRejected }) => {
const callback = status === 'resolved' ? onFulfilled : onRejected;
callback(value);
});
});
}
};
const resolve = value => settle('resolved', value);
const reject = reason => settle('rejected', reason);
this['[[PromiseStatus]]'] = 'pending';
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this['[[PromiseStatus]]'] === 'pending') {
this.#callbacks.push({ onFulfilled, onRejected });
} else {
setTimeout(() => {
const callback =
this['[[PromiseStatus]]'] === 'resolved' ? onFulfilled : onRejected;
callback(this['[[PromiseValue]]']);
});
}
}
}
class Promise {
#callbacks = [];
constructor(executor) {
const settle = (status, value) => {
if (this['[[PromiseStatus]]'] === 'pending') {
this['[[PromiseStatus]]'] = status;
this['[[PromiseValue]]'] = value;
this.#callbacks.forEach(this.#executeAsyncCallback);
}
};
const resolve = value => settle('resolved', value);
const reject = reason => settle('rejected', reason);
this['[[PromiseStatus]]'] = 'pending';
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this['[[PromiseStatus]]'] === 'pending') {
this.#callbacks.push({ onFulfilled, onRejected });
} else {
this.#executeAsyncCallback({ onFulfilled, onRejected });
}
}
#executeAsyncCallback = ({ onFulfilled, onRejected }) => {
const callback = this['[[PromiseStatus]]'] === 'resolved' ? onFulfilled : onRejected;
setTimeout(() => callback(this['[[PromiseValue]]']));
};
}
FAQ
- optional callback
- return Promise
- accept and register optional
onFulfilled
、onRejected
callback. - may be called multiple times on the same promise.
- return a promise(chaining).
- If either
onFulfilled
oronRejected
returns a valuex
,return promise according to the Promise Resolution Procedure[Resolve]](promise, x)
. - If either
onFulfilled
oronRejected
throws a exceptione
,reject promise withe
as the reason. - If
onFulfilled
is not a function andresolved
, fulfill promise with same value. - If
onRejected
is not a function andrejected
, reject promise with same reason.
- If either
Promise Resolution Procedure is an abstract operation taking as input a promise and a value: [[Resolve]](promise, x)
- If
x
andpromise
refer to the same object, reject promise withTypeError
. - If
x
is a promise/thenable, adopt its state;x.then(resolvePromise, rejectPromise)
the first takes precedence:- when
resolvePromise
with a valuey
, run[[Resolve]](promise, y)
recursively. - when
rejectPromise
with a reasonr
, reject promise withr
- if
then
throws an exceptione
, reject promise withe
- when
- Otherwise fulfill promise with
x
class Promise {
#callbacks = [];
constructor(executor) {
const settle = (status, value) => {
if (this['[[PromiseStatus]]'] === 'pending') {
this['[[PromiseStatus]]'] = status;
this['[[PromiseValue]]'] = value;
this.#callbacks.forEach(callback => callback());
}
};
const resolve = value => settle('resolved', value);
const reject = reason => settle('rejected', reason);
this['[[PromiseStatus]]'] = 'pending';
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
const executeAsyncCallback = () =>
setTimeout(() =>
this.#executeCallback(
onFulfilled,
onRejected,
promise2,
resolve,
reject
)
);
if (this['[[PromiseStatus]]'] === 'pending') {
this.#callbacks.push(executeAsyncCallback);
} else {
executeAsyncCallback();
}
});
return promise2;
}
#executeCallback = (onFulfilled, onRejected, promise, resolve, reject) => {
const [callback, settle] =
this['[[PromiseStatus]]'] === 'resolved'
? [onFulfilled, resolve]
: [onRejected, reject];
if (typeof callback === 'function') {
try {
const x = callback(this['[[PromiseValue]]']);
this.#resolutionProcedure(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
} else {
settle(this['[[PromiseValue]]']);
}
};
#resolutionProcedure = (promise, x, resolve, reject) => {
try {
if (x === promise) {
throw new TypeError('Chaining cycle detected for promise');
}
const then = x?.then;
const [resolvePromise, rejectPromise] = this.#only(
y => this.#resolutionProcedure(promise, y, resolve, reject),
reject
);
if (typeof x === 'object' && typeof then === 'function') {
try {
then.call(x, resolvePromise, rejectPromise);
} catch (e) {
rejectPromise(e);
}
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
};
#only = (...fns) => {
let callable = true;
return fns.map(fn => (...args) => {
if (callable) {
callable = false;
fn(...args);
}
});
};
catch = onRejected => this.then(null, onRejected);
}
resolve, reject
class Promise {
static reject = e => new Promise((resolve, reject) => reject(e));
static resolve(v) {
if (v instanceof Promise) return v;
return new Promise(resolve => resolve(v));
}
}
methods | |
---|---|
allsettled | all settled |
all | all fulfill |
race | one settled |
any | one fulfill |
class Promise {
static allSettled = promises =>
new Promise(resolve =>
[...promises].reduce((results, promise, index, arr) => {
Promise.resolve(promise).then(
value => {
results[index] = { status: 'fulfilled', value };
if (results.reduce(num => ++num, 0) === arr.length) {
resolve(results);
}
},
reason => {
results[index] = { status: 'rejected', reason };
if (results.reduce(num => ++num, 0) === arr.length) {
resolve(results);
}
}
);
return results;
}, [])
);
static all = promises =>
new Promise((resolve, reject) =>
[...promises].reduce((results, promise, index, arr) => {
Promise.resolve(promise).then(value => {
results[index] = value;
if (results.reduce(num => ++num, 0) === arr.length) {
resolve(results);
}
}, reject);
return results;
}, [])
);
static race = promises =>
new Promise((resolve, reject) =>
[...promises].forEach(promise =>
Promise.resolve(promise).then(resolve, reject)
)
);
}
test compliance with promises-aplus-tests
npm test;