When using Promise, our simplest understanding and usage is to provide the asynchronous result to resolve as a parameter like the above code, and then pass a custom function as the result processing function to the then method. But what exactly are the two parameters resolve and reject? Behind the scenes, what is its basic working method? Let’s take a preliminary look at it from a normative perspective.
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'foo')) .then(console.log) // foo (1s后)
TL;DR
1. The working mechanism of promise is similar to that of callback, both of which use internal abstract operation Job to implement asynchronous
2. The resolve in the Promise constructor The /reject function is created internally, and the parameter passed in when calling them is the result to be parsed, which is inserted into the Job queue together with the processing function passed in by the user that has been stored in the promise. The passed-in parameter can also be a promise, which is used internally in Promise.all/race.
3. Promise.prototype.then determines based on the current promise status whether to immediately take out the result stored in the promise and directly insert it into the Job queue together with the processing function in the parameter, or to associate it with the promise first and process it as a result. function. then will implicitly call the Promise construction function to build a new promise and return it.
4. Promise.all first creates a new promise, and then initializes an empty result array and a counter to count the resolved promises, and then iterates, and for each iteration value it will be Create a promise and set the promise's then to add the result and counter to the result array--the final result will be resolved when the counter decreases to 0.
5. Promise.race will also create a new main promise. Then, mainly based on the restriction that promise can only be resolved once, another promise will be created for each iteration value. The one that is resolved first will be resolved by the main promise first. resolve returns the result.
new Promise(executor)
First of all, let’s start with the Promise constructor. It is the value of the Promise attribute of the global object. This is why in the browser environment The reason why we can call it directly is just like the constructors of String and Array.
The first step of new Promise(executor) is just like other constructors. It builds a new object according to Promise's prototype and initializes several internal slots [[PromiseState]], [[PromiseResult] ], [[PromiseFullfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] to record some relevant information. Their functions can be roughly inferred from the names. We will mention the details below. Here, their initial values except [[PromiseResult]] are "pending", empty list, empty list, and false.
Next step, ES will generate the resolve function used to resolve the promise and the reject function used to reject the promise based on this promise object. Then call the executor with resolve function and reject function as parameters. If an error occurs during this process, reject the promise directly. Finally return promise.
So what is resolve and what is reject? We know that the state of Promise, that is, [[PromiseState]] has three values: pending, fullfilled, rejected. Use the reject function to reject the promise and change its state from pending to rejected. However, the resolve function can either fullfill a promise to change the promise's status from pending to fullfilled, or it can be used to reject a promise.
So what do resolve function and reject function do?
Let’s first look at the reject function. First, when generating it, it will initialize the [[Promise]] and [[AlreadyResolved]] slots, that is, associate it with a promise. During execution, a parameter reason will be passed in, and only when [[AlreadyResolved]] is false, that is, it has not been resolved and the status is pending, the return RejectPromise will be called and the promise and reason parameters will be passed in to reject the promise. Otherwise return undefined.
RejectPromise(promise, reason), in addition to changing [[PromiseState]] from pending to rejected, it will also set the value of the promise result [[PromiseResult]] to reason, and take out the promise's [[PromiseRejectReactions ]] (I believe readers have understood that there will be an operation to store records in this internal slot later), and use TriggerPromiseReactions to call these records for subsequent processing, and pass in the reason for rejection. Similarly, the FullfillPromise(promise, value) operation used in the resolve function changes the promise status to fulfilled, extracts the value of [[PromiseFullfillReactions]], calls TriggerPromiseReactions, and passes in the fulfilled result value.
TriggerPromiseReactions(reactions, argument) will call EnqueueJob("PromiseJobs", PromiseReactionJob, <
Let’s look at the resolve function. Like the reject function, when it is generated, it will be associated with a promise. When executing, the parameter we pass in is called resolution. If the promise has resolved, undefined is returned. The situation after that is relatively complicated.
1. If the user passes the promise itself to the resolve function as the resolution parameter, a TypeError will be created, thrown, and RejectPromise will be called. The reason parameter is this TypeError.
2. If the type of resolution is not Object, call FulfillPromise(promise, resolution).
3. The rest of the cases are when resolution is an object (Promise) with then other than itself.
If resolution is an object without then, RejectPromise.
If there is a then attribute but cannot be called, FulfillPromise, is also used.
If there is a then attribute and it can be called, just EnqueueJob("PromiseJobs", PromiseResolveThenableJob, <
Before explaining EnqueueJob, let’s first take a look at what Job is. Simply put, it is like the internal implementation mechanism of a callback: "When no other ES is running, initialize and execute its own corresponding ES." We have a FIFO Job queue to be executed, as well as the current execution environment running execution context and execution context stack. When the latter two are empty, the first Job queue will be executed.
ES stipulates that there must be at least two Job queues in the implementation, ScriptJobs and PromiseJobs. When we call EnqueueJob("PromiseJobs", ...), the Job to be completed and their parameters are inserted into the PromiseJobs queue. As you can see, there are two types of Job
1 under Promise. PromiseReactionJob(reaction, argument)
reaction has three internal slots [[Capability]], [[Type]] and [[Handler]] , respectively representing [[associated promise and related resolve function and reject function]], [[category]], [[handler]]. If the user does not provide handler(undefined), argument is used as the result depending on whether the category is Fulfill or Reject. If a handler is given, it is used for further processing of argument. Finally, use resolve function and reject function to process and return based on this result.
2. PromiseResolveThenableJob(promiseToResolve, thenable, then)
Create the resolve function and reject function associated with promiseToResolve. Use then as the calling function, thenable as this, resolve function and reject function as parameters to call and return.
Promise.prototype.then(onfulfilled, onrejected)
The first is to create a promiseCapability, which contains a new promise and the associated resolve function and reject function . The generation of promise is to build a promise just like using the Promise constructor normally, but the executor passed to the constructor is automatically created internally, and its function is to record the resolve/reject function into PromiseCapability. Create two PromiseReactions for fulfill and reject respectively based on promiseCapability and onfulfilled/onrejected, which are the final operations to be performed in PromiseJobs. If the current promise (this) is in the pending state, insert these two reactions into the promise's [[PromiseFulfillReactions]] and [[PromiseRejectReactions]] queues respectively. But if the promise is already fulfilled or rejected at this time, the value result is taken out from the [[PromiseResult]] of the promise and inserted into the Job queue as the fulfilled result/reject reason. EnqueueJob("PromiseJobs", PromiseReactionJob, <
Promise.resolve(x)
Create a promiseCapability like then, Then directly call the resolve function and pass in the value x to be resolved, and finally return the new promise.
Promise.all(iterable)
Promise.all A promiseCapability will also be created like then, which contains a new promise and its associated resolve function and reject function, and then combined with the iterator loop: 1. If the iteration is completed and the counter is 0, call the resolve function of promiseCapability to resolve Result array 2. Otherwise, the counter is incremented by 1, and then the value of the next iteration is taken out, passed to Promise.resolve and a new promise is also constructed, and then a Promise.all Resolve Element Function is created internally, and the then passed to this new promise is used to resolve Add the result to the results array and decrement the counter by one.
Promise.race(iterable)
Similarly, create a promiseCapability, then iterate, use Promise.resolve to build a new promise, and then call the then method of this new promise and pass in promiseCapability. The resolve/reject function, combined with the previously mentioned promise will only resolve once, can be seen that it is indeed very race-like.
Conclusion: After seeing this, I wonder if everyone has a deeper understanding of Promise. Going a step further, the newly proposed async/await in ES6 actually applies the ideas of Generator and Promise. If you are interested, you can continue to learn more.
The above is the detailed content of In-depth understanding of promises in JS. For more information, please follow other related articles on the PHP Chinese website!