In order to better understand and practice promise, I tried to write a class to realize all features of promise, and based on this to do some extensions, to the extent that can be used in production environment; The code is written entirely in typescript for ease of maintenance and understanding.
Directory 01.
- 02. Bottom up
- 03. How to achieve this
- 04. Promise/A + specification
- 05. More optimization
- 6. Source
- 7. The summary
- 08. Other references
02. Bottom up
02.01 Basic Concepts
- First of all let’s tidy up some
Promise
Basic concepts, including private state, internal methods, static methods, and so on.
Private property
- Private properties include state and value
PromisState
PromiseResult
, these properties are not externally accessible. - There are three types of state attributes:
pending
Initialization statefulfilled
Cash in (complete)rejected
Refused to
- Value attribute by
resolve
或reject
To deal with.
Instance methods
then
catch
finally
A static method
Promise.reject
Promise.resolve
Promise.race
Promise.all
Promise.allSettled
Promise.any
03. How to achieve this
03.01 the base class
-
After listing all the states and methods, let’s first implement a basic Promise class.
-
The most basic classes include the following core points:
- It has a private state, and it has private methods that can change the private state.
- It also takes an executor function as an argument. Inside the executor function is a pre-defined private method.
- The private state is irreversible once changed (cashed or rejected).
/** * Promise internal state enumeration */ enum PROMISE_STATES { PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected' } type PromiseStates = PROMISE_STATES.PENDING | PROMISE_STATES.FULFILLED | PROMISE_STATES.REJECTED; export const isFunction = (fn: any) :boolean= > typeof fn === 'function'; export const isObject = (obj: any) :boolean= > typeof obj === 'object'; class PromiseLike { protected PromiseState: PromiseStates; protected PromiseResult: any; constructor(executor) { this.PromiseState = PROMISE_STATES.PENDING; this.PromiseResult = undefined; executor(this._resolve, this._reject) } _resolve = (value? :any) = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } this.PromiseState = PROMISE_STATES.FULFILLED; this.PromiseResult = value; } _reject = (value? :any) = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } this.PromiseState = PROMISE_STATES.REJECTED; this.PromiseResult = value; }}Copy the code
Resolve and reject
-
The above code is easy to understand. We define the state, the actuator function, and the associated two parameters, each of which is modified by its corresponding method.
-
I forgot, however, that the Promise is asynchronous, which means that the handling of both functions should also be asynchronous; You can use setTimeout here to simulate an asynchronous process. This part can also be optimized, which we’ll cover later.
class PromiseLike { Fulfilled * /** * make the state change to fulfilled *@param value * @returns* / _resolve = (value? :any) = > { const resolveCb = () = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } this.PromiseState = FULFILLED; this.PromiseResult = value; } // Make tasks asynchronous setTimeout(resolveCb, 0); } /** * change the status to rejected *@param value* / _reject = (value? :any) = > { const rejectCb = () = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } this.PromiseState = REJECTED; this.PromiseResult = value; } setTimeout(rejectCb, 0); }}Copy the code
-
We can then implement the related static methods, because all they do is simply to modify the current internal state, so you can call the current class instantiation directly.
-
The repeated code is no longer listed. Here are the new static methods:
class PromiseLike { / /... sth static resolve(value? :any) { return new PromiseLike((resolve) = > resolve(value)); } static reject(value? :any) { return new PromiseLike((resolve, reject) = >reject(value)); }}Copy the code
-
A simple base class is done. But don’t worry, the current implementation clearly leaves a lot to be desired, and maybe even a few errors, so let’s take it a step further.
03.02 Prototype method
Promise.prototype.then
-
Everyone who knows a Promise knows the THEN method of a Promise and its chained calls. Essentially, ** it is a concrete implementation of the Thenable interface. ** this sentence is very important, we will use it later.
-
Let’s review the use of “then” first:
Promise.resolve(29).then(function fulfilled(res) { console.info(res); return res; }, function rejected(err) { console.error(err); }); Copy the code
-
The THEN method takes two arguments to process the result of resolve and reject, called the complete and reject callbacks. By default, both callback methods are registered, and only one of them may be called at a time. Even if an exception is thrown in the previous function, the second exception catching function does not immediately catch it.
- Complete the callback, receive previously
promise
的resolve
Value as the default parameter, process the corresponding data, and return a value as the nextthen
Default argument to an internal function call. - Let’s think about it a little bit,
then
Is the number of calls for registration events the same as the number of registrations? Yes. If usethen
If multiple callbacks are registered, they are executed in sequence. This means we have to add a corresponding event queue to the original. - And don’t forget,
then
Method supportsChain callsLet’s use it herereturn this
The way to simple implementation.
- Complete the callback, receive previously
-
Now let’s improve and fix the base class above.
- Define two arrays to hold complete and reject callbacks.
- The core code is listed below:
exportinterface ICallbackFn { (value? : any): any; } type CallbackParams = ICallbackFn |null; export interface IExecutorFn { (resolve: ICallbackFn, reject: ICallbackFn): any; } class PromiseLike { protected PromiseState: PromiseStates; protected PromiseResult: any; resolveCallbackQueues: Array<ICallbackFn>; rejectCallbackQueues: Array<ICallbackFn>; constructor(executor: IExecutorFn) { if(! isFunction(executor)) {throw new Error('Promise resolver undefined is not a function'); } this.PromiseState = PENDING; this.PromiseResult = undefined; // One for each array of registered events this.resolveCallbackQueues = []; this.rejectCallbackQueues = []; executor(this._resolve, this._reject); } Fulfilled * /** * make the state change to fulfilled *@param value * @returns* / _resolve = (value: any) = > { const resolveCb = () = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } while (this.resolveCallbackQueues.length) { const fn = this.resolveCallbackQueues.shift(); fn && fn(value); } this.PromiseState = FULFILLED; this.PromiseResult = value; } // Make tasks asynchronous setTimeout(resolveCb, 0); } /** * change the status to rejected *@param value* / _reject = (value: any) = > { const rejectCb = () = > { if (this.PromiseState ! == PROMISE_STATES.PENDING) {return; } while (this.rejectCallbackQueues.length) { const fn = this.rejectCallbackQueues.shift(); fn && fn(value); } this.PromiseState = REJECTED; this.PromiseResult = value; } setTimeout(rejectCb, 0); } /** * Execute the corresponding logic according to the current state * register the corresponding event if the default state * execute the corresponding event if the state changes *@param onFulfilled * @param onRejected * @returns* / then = (onFulfilled, onRejected) = > { switch (this.PromiseState) { case PENDING: isFunction(onFulfilled) && this.resolveCallbackQueues.push(onFulfilled); isFunction(onRejected) && this.rejectCallbackQueues.push(onRejected); case FULFILLED: isFunction(onFulfilled) && onFulfilled(this.PromiseResult); break; case REJECTED: isFunction(onRejected) && onRejected(this.PromiseResult); break; } return this; }}Copy the code
-
We have enriched the THEN method. But you and I both know that return this doesn’t look very reliable.
-
Let’s recall that a Promise’s private state is irreversible once changed. If an exception is thrown in the THEN method, the promise will obviously be rejected, and the state of the same instance cannot be changed again. So, chained calls to then essentially generate a new instance each time.
-
Perhaps Posting another example of using then will shed some light.
const p = Promise.resolve(123); const p1 = p.then(); const p2 = p1.then((val) = > val + 123)) const p3 = p2.then(console.info)); const p4 = p3.then(() = > { throw new Error('Oops! '); }); // Print p1, P2, P3, and p4 // Promise {<fulfilled>: 123} // Promise {<fulfilled>: 246} // Promise {<fulfilled>: undefined} // Promise {<rejected>: Error: Oops! Copy the code
-
The output of this code helps us understand what’s going on inside the THEN.
- P1: When no callback function is passed in, it simply passes the value, that is, it initializes a default handler inside, which simply passes the value.
- P2: When there is a completion callback, the value can be retrieved and processed, and the new value is passed along as a return.
- P3: If the completion callback is passed but no explicit return value, the final
promise
The value isundefined
. - p4:
promise
The status has changed torejected
It means newpromise
.As we expected.
-
With that in mind, let’s refine the THEN method.
-
First, the default handler is given in case the argument is abnormal, that is, if the argument passed is not a function or not passed.
- The completion callback is responsible for passing parameters.
- The reject callback is responsible for throwing an exception.
then = (onFulfilled? : CallbackParams, onRejected? : CallbackParams) = > { // Default processing!! onFulfilled = isFunction(onFulfilled) ? onFulfilled : value= > value; onRejected = isFunction(onRejected) ? onRejected : err= > { throw err }; } Copy the code
-
We put the two compatible processes at the top of the function to help with understanding and to simplify the logic that follows.
-
The details follow, with the core changes annotated.
class PromiseLike { /** * Execute the corresponding logic according to the current state * register the corresponding event if the default state * execute the corresponding event if the state changes *@param onFulfilled * @param onRejected * @returns* / then = (onFulfilled: CallbackParams, onRejected: CallbackParams) = > { // Default processing!! onFulfilled = isFunction(onFulfilled) ? onFulfilled : value= > value; onRejected = isFunction(onRejected) ? onRejected : err= > { throw err }; return new PromiseLike((resolve, reject) = > { /** * wrap complete callback function *@param val* / const handleFulfilled = (val: any) = > { try { const res = onFulfilled(val); resolve(res); } catch (error) { // Throw an exception if an exception occurs within the current execution logicreject(error); }};/** * Encapsulate the error callback function *@param val* / const handleRejected = (val: any) = > { try { const res = onRejected(val); reject(res); } catch(error) { reject(error); }}switch (this.PromiseState) { case PROMISE_STATES.PENDING: this.resolveCallbackQueues.push(handleFulfilled); this.rejectCallbackQueues.push(handleRejected); break; case PROMISE_STATES.FULFILLED: handleFulfilled(this.PromiseResult); break; case PROMISE_STATES.REJECTED: handleRejected(this.PromiseResult); break; }}); }}Copy the code
-
The handling of the then method is nearly complete, but there is one thing that is easily forgotten in the Promise.
- in
Promise
In the processingPromise
, the internal processing expands it to get the value. - Here’s an example to help you understand.
Promise.resolve(41) = = =Promise.resolve(Promise.resolve(41)); // false Copy the code
- in
-
I’m sorry. I’m on the wrong set. Each individually defined reference type in js is not equal.
- One more time.
const p = Promise.resolve(41); Promise.resolve(p) === p; // true Copy the code
-
Yes, if we give a promise a value, the internal mechanism will unfold it. The process is recursive, and we won’t explore it here, but remember that there are scenarios that need to be dealt with.
-
You can define a static method to determine whether a Promise is an instance, making it easier to determine later.
class PromiseLike { /** * Check whether it is an instance of the current class *@param promise * @returns* / static is(promise: PromiseType) { return promise instanceofPromiseLike; }}Copy the code
-
With this method, we can further refine the above THEN method. Pay attention to the changes, there are notes.
-
For ease of reading, only the core methods are shown (only changed here).
/** * wrap complete callback function *@param val* / const handleFulfilled = (val) = > { try { const res = onFulfilled(val); if (PromiseLike.is(res)) { // If the parameter is a Promise instance, you can pass the Promise instance directly res.then(resolve, reject); } else{ resolve(res); }}catch (error) { // Throw an exception if an exception occurs within the current execution logicreject(error); }};Copy the code
Promise.prototype.catch
-
After implementing the THEN method, the implementation of catch is easier than you can imagine.
-
Because the catch method is essentially the syntactic sugar for the second argument to the then which is the error callback function. With this understanding, it is easier to achieve.
class PromiseLike { / * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch * * * * error processing@param rejectedCb * @returns* / catch = (rejectedCb: CallbackParams) = > { return this.then(null, rejectedCb); }}Copy the code
Promise.prototype.finally
-
Finally requires us to understand several points.
- As long as the previous state is not
pending
, it must enter the execution. - Similar to the
then
, it can register multiple callbacks, each of which is executed in turn. - Unable to get internal value in callback function.
- Unless throwing an exception inside the callback changes the state to
rejected
Otherwise all it does is pass states and values.
- As long as the previous state is not
-
With that in mind, we can reuse the THEN method and pass in custom callbacks to implement it.
class PromiseLike { /** * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally * @param finallyCb * @returns* / finally = (finallyCb: CallbackParams) = > { return this.then( // When the callback is complete, the registration function is executed and the original value is passed // Encapsulate the Promise class and pass it by calling the THEN method val= > PromiseLike.resolve(finallyCb && finallyCb()).then(() = > val), // On the exception callback, the registration function is executed and the exception is thrown err= > PromiseLike.resolve(finallyCb && finallyCb()).then(() = > { throwerr }) ); }}Copy the code
-
At this point, we have implemented a few core prototyping methods.
-
Eager partners can simply instantiate an object to try it out, but promises don’t stop there, so let’s implement the corresponding static methods.
03.03 Static methods
- A custom one has already been implemented
Promise.is
Method to determine the instance. This utility class function is simple and practical and can be kept. - There are also two fast instantiations
Promise
Class method we also implemented:Promise.resolve
和Promise.reject
.Here’s a little improvement.
Promise.resolve
-
Now that we’ve defined the promise.is method, and we know a little bit more about promises, we don’t need to do any more processing if we pass in a Promise instance. So this method needs a little bit of compatibility.
class PromiseLike { / direct instantiation proimse * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve * * * *@param value * @returns* / static resolve(value? :any) { if (PromiseLike.is(value)) { return value; } return new PromiseLike((resolve) = >resolve(value)); }}Copy the code
-
Now we can try to implement the remaining two class methods provided by Promise promise. all, promise.race.
Promise.all
-
This method receives an array of Promise instances and returns a Promise instance whose value is an array of the resolve values of all Promise instances.
- When one of them
Promise
有reject
The value of the,Promise.all
Will return to the firstrejected
The value of the. - Wait until all the
Promise
resolve
After that,Promise
. All will return the result. Promise.all
It also supports chained calls.
- When one of them
-
It might be a little hard to read in plain English, but let’s just look at the case.
Promise.all([Promise.resolve(1), Promise.reject(2)]); // Promise {<rejected>: 2} Promise.all([Promise.resolve(1), Promise.resolve(2)]); // Promise {<fulfilled>: Array(2)} [1, 2] Promise.all([]); // Promise {<fulfilled>: Array(2)} Copy the code
-
The result of the third expression is important to understand the difference between promise. race and promise. all. I’ll talk about that later. Beyond that, the results are obvious.
-
Promise.all returns the order of the arguments passed into the array, which can also be interpreted as sequential execution, and fills in the corresponding positions. Based on these points, there is a train of thought to implement it.
- Execute all in sequence
Promise
And save the result to the corresponding position of the array, and count the number of executed; When the number equals the length of the array passed in, an array of the results is returned.
class PromiseLike { /** * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all * @param Technically, promises arguments are iterable, but to simplify the implementation, they're an array *@returns* / static all(promises: Array<ICallbackFn>) { // Support chained calls return new PromiseLike((resolve, reject) = > { const len = promises.length; let resolvedPromisesCount = 0; let resolvedPromisesResult = <any> [];for (let i = 0; i < len; i++) { const currentPromise = promises[i]; // If it is not a Promise instance, you need to wrap one; // But since the effect of wrapping the Promise class directly is idempotent, there is no need to judge PromiseLike.resolve(currentPromise) .then((res: any) = > { resolvedPromisesCount++; resolvedPromisesResult[i] = res; // Return the array when all values are resolved if(resolvedPromisesCount === len) { resolve(resolvedPromisesResult); }})// If there is any exception, then deduce directly .catch((err: any) = >{ reject(err); }); }}); }}Copy the code
- Execute all in sequence
-
As stated in the method comments, the promise. all and promise. race methods accept arguments that are iterable objects, not just arrays. Here, to facilitate implementation, use an array instead. Iterables are outside the core scope of this article, so if you’re interested, follow the links above.
Promise.race
-
Promise.race is somewhat similar to Promise.all, at least as far as parameters are concerned, in that both accept an iterable as an argument and can be called chained, meaning that it also returns a new Promise instance.
-
In contrast, promise. race returns either the first promise. resolve value, or the first reject value, which is not an array.
-
With these two points in mind, there is a clear idea for implementation.
- The traversal sequence executes all promises and fetters the value of the first resolve.
class PromiseLike { /** * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race * @param promises * @returns* / static race(promises: Array<ICallbackFn>) { return new PromiseLike((resolve, reject) = > { for (let i = 0; i < promises.length; i++) { const currentPromise = promises[i]; PromiseLike.resolve(currentPromise) .then((res: any) = > { resolve(res); }) .catch((err: any) = >{ reject(err); }); }}); }}Copy the code
-
Run that code again, and you shouldn’t be surprised what you get.
Promise.race([]); // Promise {
} has a different result than promise.all Copy the code -
So far, we have implemented both of the core methods that are widely compatible. Does that mean it’s time to have fun? Sure. But now that we’re here, there are a few more Promise methods we can implement, both to hone our hands-on skills and to prove that we’re putting what we’ve learned into practice.
03.04 Other static methods
Promise.allSettled
-
This method is very similar to promise.all, in that it executes all Promise instances and returns all results, regardless of the result, with an object in the returned array.
- Each object has only two properties
status
和value
或reason
; If the currentproimse
是fulfilled
The attribute isstatus
和value
, if the current isrejected
The attribute isstatus
和reason
.
- Each object has only two properties
-
With a few changes to promise. all.
- The logic for determining the count is done in both callbacks, and the return value is wrapped.
/** * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled * @param Technically, promises arguments are iterable, but to simplify the implementation, they're an array *@returns* / static allSettled(promises: Array<IPromiseType>) { // Support chained calls return new PromiseLike((resolve, reject) = > { const len = promises.length; const startTime = Date.now(); let resolvedPromisesCount = 0; let resolvedPromisesResult = <any> [];for (let i = 0; i < len; i++) { const currentPromise = promises[i]; // If it is not a Promise instance, you need to wrap one; // But since the effect of wrapping the Promise class directly is idempotent, there is no need to judge PromiseLike.resolve(currentPromise) .then((res: any) = > { resolvedPromisesCount++; resolvedPromisesResult[i] = { status: PROMISE_STATES.FULFILLED, value: res }; // When all promises are complete, return the array; One more property is encapsulated to show the execution time if (resolvedPromisesCount === len) { resolvedPromisesResult.duringTime = Date.now() - startTime + 'ms'; resolve(resolvedPromisesResult); }})// If there is any exception, then deduce directly .catch((err: any) = > { resolvedPromisesCount++; resolvedPromisesResult[i] = { status: PROMISE_STATES.REJECTED, reason: err }; if (resolvedPromisesCount === len) { resolvedPromisesResult.duringTime = Date.now() - startTime + 'ms'; resolve(resolvedPromisesResult); }}); }}); }Copy the code
Promise.any
-
This is the new API that was just drafted this year (2021). The definition is similar to promise.race in that it takes an iterable as an argument and can be called in chains.
-
The difference is that it returns the first settled value, which is resolve; If all incoming Promises are in the rejected state, it waits until all the rejected states are complete before returning an object composed of rejection errors. This object is a newly defined type, AggregateError, so I’m just going to use it without expanding it.
-
By definition, it is similar to promise. race, but by implementation, it is more similar to promise. all.
- You just need to move the logic for counting the number into the error callback and return it to the error object.
class PromiseLike { / * * * 2021 just regulate any * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any *@param promises * @returns* / static any(promises: Array<ICallbackFn>) { return new PromiseLike((resolve, reject) = > { const len = promises.length; let rejectedPromisesCount = 0; let rejectedPromisesResult = <any>[]; for (let i = 0; i < promises.length; i++) { const currentPromise = promises[i]; PromiseLike.resolve(currentPromise) .then((res: any) = > { resolve(res); }) .catch((err: any) = > { rejectedPromisesCount++; rejectedPromisesResult[i] = err; if (rejectedPromisesCount === len) { // Throw the new object directly if the browser supports it, otherwise throw the exception directly if (isFunction(AggregateError)) { throw new AggregateError(rejectedPromisesResult, 'All promises were rejected'); } else { throw(rejectedPromisesResult); }}}); }}}})Copy the code
04. Promise/A + specification
04.01 promises – aplus – tests to verify
-
Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+ : Promises /A+
-
It is simple to inject a method that returns a Promise/resolve/reject object.
-
Since we write it as a class, we can simply add a static function.
class PromiseLike { /** * Third-party library validation *@returns* / static deferred() { let defer: any = {}; defer.promise = new PromiseLike((resolve, reject) = > { defer.resolve = resolve; defer.reject = reject; }); returndefer; }}Copy the code
-
It is important to use the CommonJS specification to export, otherwise an error will be reported.
module.exports = PromiseLike; Copy the code
-
Run the NPX Promises -aplus-tests directory name to verify.
04.02 not perfect (compatible fix)
- The result shows that some cases are not passed. Terrible! Let’s take them one by one.
‘Chaining cycle detected for promise’
-
This exception tells us that we cannot use ourselves in a Promise, otherwise we will create an infinite loop.
-
Here’s an example:
const p = Promise.resolve(1).then(() = > p); Copy the code
-
When you run this code, you get the above error.
-
The solution is not that difficult. Define variables to hold the return value of the then function, and be compatible with the position returned by the internal method, and throw an exception if it is equal.
const res = onFulfilled(val); // The promise returned must not be a current promise. Otherwise, an infinite loop will be created if (newPromise === res) { throw new TypeError('Chaining cycle detected for promise #<Promise>'); } Copy the code
2.3.3: Otherwise, the if
x is an object or function
-
Again, it turns out that the Promise specification also has special treatment for passing in objects and function types. We did not handle it, so the above error occurred. This is defined in the specification, which simply means that if the argument passed is Thenable, then the then method needs to be called. As mentioned above, there is no escaping it.
-
Having previously only handled instances of the Promise specially, we now realize that we need to handle objects of the Thenable interface as well. But because the Promise instance itself is a special object that implements the Thenable interface. (typeof Promise.resolve(1); // object), so the implementation of the Thenable interface processing, naturally can also cover the original logic.
-
New define a separate method to implement to improve readability.
- This function is a bit complicated, but every piece of logic can be traced back in the specification.
/ * * * the implementation to follow the Promise/A + specification * https://github.com/promises-aplus/promises-spec *@param promise * @param x * @param resolve * @param reject * @returns* / const resolvePromise = (promise: any, x: any, resolve: ICallbackFn, reject: ICallbackFn) = > { // The promise returned must not be a current promise. Otherwise, an infinite loop will be created if (newPromise === x) { reject(new TypeError('Chaining cycle detected for promise #<Promise>')); } // Object that might be implemented by the thenable interface if (isObject(x) || isFunction(x)) { if (x === null) { return resolve(x); } let thenCb; try { thenCb = x.then; } catch (error) { return reject(error); } // If it is an object of thenable, its THEN method is called // This step covers the possibility of a Promise instance if (isFunction(thenCb)) { let isCalled = false; try { thenCb.call( x, // Point to the current function or object (y: any) = > { // If both resolvePromise and rejectPromise can be called // Then only the first time (resolvePromise or rejectPromise) is called and no further execution is required if (isCalled) return; isCalled = true; // Pass in the current function to implement the recursive expansion call resolvePromise(promise, y, resolve, reject); }, (r: any) = > { // After any previous call, no further logic is required if (isCalled) return; isCalled = true; reject(r); })}catch (error) { if (isCalled) return; reject(error); }}else{ resolve(x); }}else{ resolve(x); }}Copy the code
-
Replace the resolvePromise function where the data was processed.
-
Well, that’s a complete pass.
872 passing (14s) Copy the code
05. More optimization
05.01 queueMicrosoft
- In the process of learning, serendipity
queueMicrosoft
This method is used to convert tasks into microtasks. We know thatsetTimeout
Although asynchronous effects can be achieved, it belongs to the macro task, andPromise
The microtask does not match. So you can usequeueMicrosoft
To replace. - You can see how to use it here
05.02 typescript perfect
-
Many interfaces have been defined in the previous examples. Here is an example to improve a ha, more details can be viewed in the source code below.
export interface IPromiseType { then: IExecutorFn; catch: ICallbackFn; finally: ICallbackFn; } class PromiseLike implements IPromiseType {} Copy the code
-
Since you’re still learning your typescript practices, there may be a lot of improvements and optimizations in your source code that you can point out in your comments or issues, and any improvements that make sense will be taken and practiced.
-
Version used: “typescript”: “^4.3.5”
05.03 Fancy variant methods
Promise.last
-
Define a function that returns the last completed promise and optionally whether or not you need a promise you rejected.
/** * returns the last completed value, optionally ignoring the exception or not * if not, the exception is thrown first * if ignored, the completion value is returned *@param promises * @param ignoreRejected * @returns* / static last(promises: Array<IPromiseType>, ignoreRejected: boolean = false) { return new PromiseLike((resolve, reject) = > { const len = promises.length; const startTime = Date.now(); let resolvedPromisesCount = 0; for (let i = 0; i < len; i++) { const currentPromise = promises[i]; PromiseLike.resolve(currentPromise) .then((res: any) = > { resolvedPromisesCount++; // When all promises are complete, return the last value; Encapsulates a property to display execution time if (resolvedPromisesCount === len) { isObject(res) && (res.duringTime = Date.now() - startTime + 'ms'); resolve(res); }})// If there is any exception, then deduce directly .catch((err: any) = > { if (ignoreRejected) { resolvedPromisesCount++; } else{ reject(err) } }); }}); }Copy the code
-
It is also possible to implement a variant that returns the last updated value, whether in the fulfilled or rejected state. The source code is shown, here is no longer repeated.
Promise.wrap
-
This method can wrap the original ordinary asynchronous request into a Promise instance for easy chaining calls, etc.
-
Suppose you have such a request handler.
function fn(url, cb) { ajax(url, cb); } Copy the code
-
To make it possible to use chained calls, see the comments.
/** * Wrap a non-Promise instance into a Promise instance * such as an Ajax request * const request = promise.wrap (ajax); * request.then(callback); *@param fn * @returns* / static wrap(fn: any) { if(! isFunction(fn)) {return fn; } return function () { const args: any[] = Array.prototype.slice.call(arguments); return new PromiseLike((resolve) = > { fn.apply(null, args.concat(function (res: any, err: any) { res && resolve(res); err && resolve(err); })); }}})Copy the code
Promise.sequence
-
The ability to chain calls can be combined with arrays of Reduce to complete serial operations, passing functions into new functions.
- The parameters here are not involved
Promise
Instance, using chained calls.
/** * returns a function to execute *@param fns * @returns* / static sequence(fns: Array<ICallbackFn>) { return (x: number) = > fns.reduce((acc, fn: ICallbackFn) = > { if(! isFunction(fn)) { fn =x= > x; } return acc.then(fn).catch((err: any) = > { throw err }); }, PromiseLike.resolve(x)); } Copy the code
- The parameters here are not involved
-
Let’s say we have multiple functions, and we can combine them by doing something like this, which is the processing function.
function addThree(x) { return x + 3; } function addFive(x) { return x + 5; } const addEight = ProimseLike.sequence([addThree, addFive]); addEight(2); / / 10 Copy the code
-
The above function actually implements serialization; You can also make some changes to save each value in order.
Promise.sequenceByOrder
-
This method executes the functions in order and returns the values in order of completion.
/** * Execute all promises sequentially and return an array in the order they are returned. Note that the arguments are an array of functions that return promise instances@param promises * @returns* / static sequenceByOrder(promises: Array<ICallbackFn>) { return new PromiseLike((resolve) = > { let promiseResults: any = []; const reduceRes = promises.reduce((prevPromise, currentPromise: ICallbackFn, currentIndex: number) = > { return prevPromise.then((val: any) = > { promiseResults.push(val); const newVal = currentPromise(val); // Save the last loop and discard the first value (default: undefined) if (currentIndex === promises.length - 1) { promiseResults.unshift(); } return newVal; }); }, PromiseLike.resolve()); reduceRes.then((val: any) = > { promiseResults.push(val); resolve(promiseResults); }); }); } Copy the code
Promise.map
-
Define a function that handles all promise values, similar to the map method for arrays.
* promise.map ([p1, p2, p3], (val, resolve) => {* resolve(val + 1); * *})@param promises * @param fn * @returns* / static map(promises: Array<IPromiseType>, fn: any) { return PromiseLike.all(promises.map((currentPromise) = > { return new PromiseLike((resolve) = > { if(! isFunction(fn)) { fn =(val:any, resolve: ICallbackFn) = >resolve(val); } fn(currentPromise, resolve); })})); }Copy the code
Promise.observe
-
Define a function to clean up promise-related side effects, usually used in promise.race. Suppose we use promise.race to set timeouts, but still want the data to be processed in the timeout scenario.
/** * promise.race ([promise.observe (p, cleanup // handler), timeoutFn // timeout function)) *@param promise * @param fn * @returns* / static observe(promise: IPromiseType, fn: ICallbackFn) { promise .then((res: any) = > { PromiseLike.resolve(res).then(fn); }, (err) = > { PromiseLike.resolve(err).then(fn); }); return promise; } Copy the code
6. Source
06.01 Part source code
-
All source code is not much, all posted out more also occupy the page. Here is the full implementation of THEN.
class PromiseLike { /** * Execute the corresponding logic according to the current state * register the corresponding event if the default state * execute the corresponding event if the state changes *@param onFulfilled * @param onRejected * @returns* / then = (onFulfilled? : CallbackParams, onRejected? : CallbackParams) = > { // Default processing!! onFulfilled = isFunction(onFulfilled) ? onFulfilled : value= > value; onRejected = isFunction(onRejected) ? onRejected : err= > { throw err }; / * * * the implementation to follow the Promise/A + specification * https://github.com/promises-aplus/promises-spec *@param promise * @param x * @param resolve * @param reject * @returns* / const resolvePromise = (promise: IPromiseType, x: any, resolve: ICallbackFn, reject: ICallbackFn) = > { // The promise returned must not be a current promise. Otherwise, an infinite loop will be created if (newPromise === x) { reject(new TypeError('Chaining cycle detected for promise #<Promise>')); } // Object that might be implemented by the thenable interface if (isObject(x) || isFunction(x)) { if (x === null) { return resolve(x); } let thenCb; try { thenCb = x.then; } catch (error) { return reject(error); } // If it is an object of thenable, its THEN method is called // This step covers the possibility of a Promise instance if (isFunction(thenCb)) { let isCalled = false; try { thenCb.call( x, // Point to the current function or object (y: any) = > { // If both resolvePromise and rejectPromise can be called // Then only the first time (resolvePromise or rejectPromise) is called and no further execution is required if (isCalled) return; isCalled = true; // Pass in the current function to implement the recursive expansion call resolvePromise(promise, y, resolve, reject); }, (r: any) = > { // After any previous call, no further logic is required if (isCalled) return; isCalled = true; reject(r); })}catch (error) { if (isCalled) return; reject(error); }}else{ resolve(x); }}else{ resolve(x); }}// Define variables to pass arguments for comparison const newPromise = new PromiseLike((resolve, reject) = > { /** * wrap complete callback function *@param val* / const handleFulfilled = (val: any) = > { try { const x = onFulfilled && onFulfilled(val); resolvePromise(newPromise, x, resolve, reject); } catch (error) { // Throw an exception if an exception occurs within the current execution logic reject(error); }; }; /** * Encapsulate the error callback function *@param val* / const handleRejected = (val: any) = > { try { const x = onRejected && onRejected(val); resolvePromise(newPromise, x, resolve, reject); } catch(error) { reject(error); }}switch (this.PromiseState) { case PROMISE_STATES.PENDING: this.resolveCallbackQueues.push(handleFulfilled); this.rejectCallbackQueues.push(handleRejected); break; case PROMISE_STATES.FULFILLED: handleFulfilled(this.PromiseResult); break; case PROMISE_STATES.REJECTED: handleRejected(this.PromiseResult); break; }});returnnewPromise; }}Copy the code
06.02 all source
- Making the address
7. The summary
- In this attempt to implement
Promise
In the process, he is also in the side to write and learn. This is the whole idea that I understand and practice, and it doesn’t necessarily apply to others; Hopefully it can serve as a reference, inspiration or influence to others. Before writing this article, I didn’t expect to spend two full days just scratching the surface. whilePromise
There’s obviously a lot to talk about internally, the microtasks involved,async/await
Correlations, iterators and generators; Just the current energy limit, let’s stop here. The iterator and generator contents may be parsed later. - The whole practice, is also to practice
typescipt
Process, here I use the class way to write, mainly to facilitate their understanding; But it’s also perfectly functional.typescript
The compiled code is the implementation of the function, and is js. You can directly view the following address to understand. In addition, the compilation and transformation of the content is also worth exploring.- Github
08. Other references
08.01 Reference
Typescript
Elegant asynchronous processing
Built-in object Promise
Promise/A+
Promise to realize