-
The class implementation:
class MPromise { constructor(){}}Copy the code
-
Define three state types
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; Copy the code
-
Setting the initial state
class MPromise { constructor() { // The initial state is pending this.status = PENDING; this.value = null; this.reason = null; }}Copy the code
-
Resolve and Reject methods
- According to the specification, these two methods change status from Pending to pity/Rejected.
- Note that the input arguments to both functions are value and reason.
class MPromise { constructor() { // The initial state is pending this.status = PENDING; this.value = null; this.reason = null; } resolve(value) { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; }}reject(reason) { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; }}}Copy the code
-
If you find that our promise is missing, let’s add it
- The input parameter is a function that takes resolve and reject.
- Note that when a promise is initialized, this function is executed and any errors are thrown through Reject
class MPromise { constructor(fn) { // The initial state is pending this.status = PENDING; this.value = null; this.reason = null; try { fn(this.resolve.bind(this), this.reject.bind(this)); } catch (e) { this.reject(e); }}resolve(value) { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; }}reject(reason) { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; }}}Copy the code
-
Let’s implement the key THEN method
- Then receives two parameters, onFulfilled and onRejected
then(onFulfilled, onRejected) {} Copy the code
- Check and process the arguments, ignoring the previous ones if they are not function. This ignore refers to the return of value or Reason as is.
isFunction(param) { return typeof param === 'function'; } then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason }; } Copy the code
- Depending on the current state of the promise, different functions are called
then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason; }; switch (this.status) { case FULFILLED: { fulFilledFn(this.value); break; } case REJECTED: { rejectedFn(this.reason); break; }}}Copy the code
-
If you write it this way, it will be executed the moment the then function is called. This is a pity or Rejected. What if status is not fulfilled or Rejected? It may still be pending.
- So we need to get all the callbacks first, and then we can execute them at some point. Create two new arrays to store successful and failed callbacks, respectively, and then, if pending.
then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason; }; switch (this.status) { case FULFILLED: { fulFilledFn(this.value); break; } case REJECTED: { rejectedFn(this.reason); break; } case PENDING: { this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled); this.REJECTED_CALLBACK_LIST.push(realOnRejected); break; }}}Copy the code
- When status changes, all callbacks are executed. Let’s use the getters and setters for ES6. This is more semantically appropriate for what to do when status changes.
get status() { return this._status; } set status(newStatus) { switch (newStatus) { case FULFILLED: { this.FULFILLED_CALLBACK_LIST.forEach(callback= > { callback(this.value); }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback= > { callback(this.reason); }); break; }}}Copy the code
-
The return value of then is a bit too much, so I’ll leave it out
7.1 If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return rejection cause E. (In this case, we need a manual catch code, reject when we encounter an error.)
then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason; }; const fulFilledFnWithCatch = (resolve, reject) = > { try { fulFilledFn(this.value); } catch (e) { reject(e) } }; const rejectedFnWithCatch = (resolve, reject) = > { try { rejectedFn(this.reason); } catch(e) { reject(e); }}switch (this.status) { case FULFILLED: { return new MPromise(fulFilledFnWithCatch); } case REJECTED: { return new MPromise(rejectedFnWithCatch); } case PENDING: { return new MPromise((resolve, reject) = > { this.FULFILLED_CALLBACK_LIST.push(() = > fulFilledFnWithCatch(resolve, reject)); this.REJECTED_CALLBACK_LIST.push(() = >rejectedFnWithCatch(resolve, reject)); }); }}}Copy the code
7.2 If ondepressing is not a function and promise1 performs successfully, promise2 must perform successfully and return the same value
const fulFilledFnWithCatch = (resolve, reject) = > { try { fulFilledFn(this.value); resolve(this.value); } catch (e) { reject(e) } }; Copy the code
7.3 If onRejected is not a function and promise1 rejects it, promise2 must reject it and return the same data.
If onRejected succeeds, promise2 should be resolved
const rejectedFnWithCatch = (resolve, reject) = > { try { rejectedFn(this.reason); if (this.isFunction(onRejected)) { resolve(); }}catch(e) { reject(e); }}Copy the code
7.4 If onFulfilled or onRejected returns a value x, then run the resolvePromise method
then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason; }; const fulFilledFnWithCatch = (resolve, reject, newPromise) = > { try { if (!this.isFunction(onFulfilled)) { resolve(this.value); } else { const x = fulFilledFn(this.value); this.resolvePromise(newPromise, x, resolve, reject); }}catch (e) { reject(e) } }; const rejectedFnWithCatch = (resolve, reject, newPromise) = > { try { if (!this.isFunction(onRejected)) { reject(this.reason); } else { const x = rejectedFn(this.reason); this.resolvePromise(newPromise, x, resolve, reject); }}catch(e) { reject(e); }}switch (this.status) { case FULFILLED: { const newPromise = new MPromise((resolve, reject) = > fulFilledFnWithCatch(resolve, reject, newPromise)); return newPromise; } case REJECTED: { const newPromise = new MPromise((resolve, reject) = > rejectedFnWithCatch(resolve, reject, newPromise)); return newPromise; } case PENDING: { const newPromise = new MPromise((resolve, reject) = > { this.FULFILLED_CALLBACK_LIST.push(() = > fulFilledFnWithCatch(resolve, reject, newPromise)); this.REJECTED_CALLBACK_LIST.push(() = > rejectedFnWithCatch(resolve, reject, newPromise)); }); returnnewPromise; }}}Copy the code
-
resolvePromise
resolvePromise(newPromise, x, resolve, reject) { // If newPromise and x point to the same object, reject newPromise as TypeError // This is to prevent endless loops if (newPromise === x) { return reject(new TypeError('The promise and the return value are the same')); } if (x instanceof MPromise) { // If x is a Promise, make newPromise accept the state of x // Continue to execute x, and if you get a y, continue to parse y // If then then execute x.then((y) = > { resolvePromise(newPromise, y, resolve, reject); }, reject); } else if (typeof x === 'object' || this.isFunction(x)) { // If x is an object or function // If x is null, resolve if (x === null) { return resolve(x); } let then = null; try { // assign x. teng to then then = x.then; } catch (error) { // If an error e is thrown when taking the value x. teng, reject a promise based on e return reject(error); } // If then is a function if (this.isFunction(then)) { let called = false; // call this with x as the function's scope // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise // The name is the same, I use the anonymous function directly try { then.call( x, // If resolvePromise is called with the value y, run resolvePromise (y) = > { // If both resolvePromise and rejectPromise are invoked, // Or if the same argument is called more than once, the first call takes precedence and the rest are ignored // Implement this by adding a variable called before it if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, // If rejectPromise is invoked with argument r, reject the promise with argument r (r) = > { if (called) return; called = true; reject(r); }); } catch (error) { // If calling the then method throws an exception e: // If resolvePromise or rejectPromise has already been invoked, ignore it if (called) return; // Otherwise use e as the basis for rejecting the promisereject(error); }}else { // If then is not a function, execute a promise with an x argumentresolve(x); }}else { // If x is not an object or function, execute a promise with x as an argumentresolve(x); }}Copy the code
-
OnFulfilled and onRejected are micro tasks
We can wrap the function in queueMicrotask
then(onFulfilled, onRejected) { const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) = > { return value; } const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) = > { throw reason; }; const fulFilledFnWithCatch = (resolve, reject, newPromise) = > { queueMicrotask(() = > { try { if (!this.isFunction(onFulfilled)) { resolve(this.value); } else { const x = fulFilledFn(this.value); this.resolvePromise(newPromise, x, resolve, reject); }}catch (e) { reject(e) } }) }; const rejectedFnWithCatch = (resolve, reject, newPromise) = > { queueMicrotask(() = > { try { if (!this.isFunction(onRejected)) { reject(this.reason); } else { const x = rejectedFn(this.reason); this.resolvePromise(newPromise, x, resolve, reject); }}catch(e) { reject(e); }})}switch (this.status) { case FULFILLED: { const newPromise = new MPromise((resolve, reject) = > fulFilledFnWithCatch(resolve, reject, newPromise)); return newPromise; } case REJECTED: { const newPromise = new MPromise((resolve, reject) = > rejectedFnWithCatch(resolve, reject, newPromise)); return newPromise; } case PENDING: { const newPromise = new MPromise((resolve, reject) = > { this.FULFILLED_CALLBACK_LIST.push(() = > fulFilledFnWithCatch(resolve, reject, newPromise)); this.REJECTED_CALLBACK_LIST.push(() = > rejectedFnWithCatch(resolve, reject, newPromise)); }); returnnewPromise; }}}Copy the code
-
Let’s just write some code and test it out
const test = new MPromise((resolve, reject) = > { setTimeout(() = > { resolve(111); }, 1000); }).then(console.log); console.log(test); setTimeout(() = > { console.log(test); }, 2000) Copy the code
And that’s why I can call.then, but I can’t call.catch? Because we’re not declaring a catch method in our class
-
Catch method
catch (onRejected) { return this.then(null, onRejected); } Copy the code
-
promise.resolve
The existing object will be fulfilled as a Promise object. If the parameter of the Promise. Resolve method is not an object with the then method (also called thenable object), a new Promise object will be returned, and its state will be fulfilled.
static resolve(param) { if (param instanceof MyPromise) { return param; } return new MyPromise(function (resolve) { resolve(param); }); } Copy the code
-
promise.reject
Returns a new Promise instance with the status Rejected. The reason argument to the promise. reject method is passed to the instance callback.
static reject(reason) { return new MPromise((resolve, reject) = > { reject(reason); }); } Copy the code
-
promise.race
const p = Promise.race([p1, p2, p3]);
This method wraps multiple Promise instances into a new Promise instance. If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.
static race(promiseList) { return new MPromise((resolve, reject) = > { const length = promiseList.length; if (length === 0) { return resolve(); } else { for (let i = 0; i < length; i++) { MPromise.resolve(promiseList[i]).then( (value) = > { return resolve(value); }, (reason) = > { returnreject(reason); }); }}}); }Copy the code
Write some test code
const test = new MPromise((resolve, reject) = > { setTimeout(() = > { resolve(111); }, 1000); }); const test2 = new MPromise((resolve, reject) = > { setTimeout(() = > { resolve(222); }, 2000); }); const test3 = new MPromise((resolve, reject) = > { setTimeout(() = > { resolve(333); }, 3000); }); MPromise.race([test, test2, test3]).then(console.log); Copy the code