This article comes from NetEase cloud community.
A while ago recorded some of the general usage of promise, this article went further to a level, to analyze and analyze how this rule mechanism of promise is implemented. PS: This article is suitable for people who have some knowledge of the usage of promise, and if you are not familiar with its usage, you can step into my previous blog post .
The Promise source code in this article is written according to the promise/a+ specification (do not want to see the English version of the promise/a+ standard Chinese translation )
Introduction
In order to make it easier for everyone to understand, we start from a scene to explain, so that we can follow the thought of thinking, I believe you will be easier to read.
Consider the following request handling for obtaining a user ID
//例1function getUserId() { return new Promise(function(resolve) { //异步请求 http.get(url, function(results) { resolve(results.id) }) })}getUserId().then(function(id) { //一些处理})
getUserId
The method returns a promise
callback that can be then
registered by its method (note 注册
the word) promise
when the asynchronous operation succeeds. This way of execution makes asynchronous calls very handy.
Principle Analysis
So how does this function happen Promise
? In fact, according to the above sentence, the realization of a most basic embryonic form is very easy.
Minimalist Promise Prototype
function Promise(fn) { var value = null, callbacks = []; //callbacks为数组,因为可能同时有很多个回调 this.then = function (onFulfilled) { callbacks.push(onFulfilled); }; function resolve(value) { callbacks.forEach(function (callback) { callback(value); }); } fn(resolve);}
The above code is simple, and the general logic is this:
- Call the
then
method, will want to Promise
execute when the asynchronous operation succeeds the callback to put in the callbacks
queue, actually is registers the callback function, may think to the observer pattern direction;
- When an instance is created, the
Promise
function passed in will be given a parameter of the function type, that is resolve
, it receives a parameter value, which represents the result returned by the asynchronous operation, and when the one-step operation succeeds, the user invokes the resolve
method, and the actual action is to callbacks
actually perform the Callback one by one in the queue is executed;
Can be combined with 例1
the code to see, first new Promise
, promise
the passed function sends an asynchronous request, then invokes the promise
object's then
properties, registers the request for a successful callback function, and then when the asynchronous request is sent successfully, resolve(results.id)
the method executes then
the callback array that the method registers.
Believe that careful people should be able to see that the then
method should be capable of chaining calls, but the above basic simple version obviously cannot support chained calls. It is then
also very simple to make the method support chained calls:
this.then = function (onFulfilled) { callbacks.push(onFulfilled); return this;};
See? A simple sentence can be implemented like the following chained calls:
// 例2getUserId().then(function (id) { // 一些处理}).then(function (id) { // 一些处理});
Add delay mechanism
The careful classmate should find that the above code may still have a problem: if the then
function is executed before the method registers the callback, resolve
what should I do? For example, promise
internal functions are synchronous functions:
// 例3function getUserId() { return new Promise(function (resolve) { resolve(9876); });}getUserId().then(function (id) { // 一些处理});
This is clearly not allowed, and the Promises/A+
specification explicitly requires callbacks to be executed asynchronously to ensure a consistent and reliable order of execution. So we have to add some processing to ensure that the resolve
then
method has registered all callbacks before execution. We can do this by transforming the following resolve
function:
function resolve(value) { setTimeout(function() { callbacks.forEach(function (callback) { callback(value); }); }, 0)}
The idea of the above code is also very simple, is through the setTimeout
mechanism, the execution of the resolve
callback logic to the end of the JS
task queue to ensure that at the resolve
time of execution, the then
callback function of the method has been registered to complete.
However, there seems to be a problem here: if the Promise
asynchronous operation has succeeded, then the callback registered before the asynchronous operation succeeds, but the callback that is Promise
called after the asynchronous operation succeeds then
will never be executed again, which is obviously not what we want.
Join status
Well, in order to solve the problem thrown in the previous section, we have to join the state mechanism, which is known to all, pending
fulfilled
rejected
.
Promises/A+
The 2.1 in the specification Promise States
explicitly stipulates that it pending
can be converted to fulfilled
or rejected
only once, that is, if it is converted to a pending
fulfilled
state, then it cannot be converted to rejected
. and fulfilled
the rejected
state can only be transformed from one pending
another and cannot be converted between the two. A picture wins thousands of words:
The improved code is this:
function Promise(fn) { var state = 'pending', value = null, callbacks = []; this.then = function (onFulfilled) { if (state === 'pending') { callbacks.push(onFulfilled); return this; } onFulfilled(value); return this; }; function resolve(newValue) { value = newValue; state = 'fulfilled'; setTimeout(function () { callbacks.forEach(function (callback) { callback(value); }); }, 0); } fn(resolve);}
The idea of the above code is this: When executed, the state is set to the new callback that was added after that, and is resolve
fulfilled
then
executed immediately.
There's no place to state
set it up rejected
, and in order to focus on the core code, there's a section behind this question that will be added.
Chain-type Promise
So here's the problem again, if the user again then the registration of the function is still one Promise
, how to solve? For example, the following 例4
:
// 例4getUserId() .then(getUserJobById) .then(function (job) { // 对job的处理 });function getUserJobById(id) { return new Promise(function (resolve) { http.get(baseUrl + id, function(job) { resolve(job); }); });}
This scenario is believed to promise
be used by people who know there will be many, so the so-called chain-like Promise
.
A chain Promise
is the promise
fulfilled
beginning of the next promise
(rear-neighbor) after the current state is reached promise
. So how do we converge on promise
the current and the neighboring? promise
(This is the difficulty here).
In fact, it is not spicy, as long as the then
method inside return
a promise
good. Promises/A+
the 2.2.7 in the norm is so-called (smiling face) ~
Below to see the hidden mystery of the then
method and resolve
method to retrofit code:
function Promise (FN) {var state = ' pending ', value = null, callbacks = []; This.then = function (onfulfilled) {return new Promise (function (resolve) {handle ({OnF ulfilled:onfulfilled | | NULL, resolve:resolve}); }); }; function handle (callback) {if (state = = = ' pending ') {Callbacks.push (callback); Return }//If then nothing passes anything if (!callback.onfulfilled) {callback.resolve (value); Return } var ret = callback.onfulfilled (value); Callback.resolve (ret); } function Resolve (NewValue) {if (NewValue && (typeof newvalue = = = ' object ' | | typeof newvalue = = = ' Fun Ction ') {var then = Newvalue.then; if (typeof then = = = ' function ') {Then.call (newvalue, resolve); Return }} state = ' fulfilled '; Value = newvalUe SetTimeout (function () {Callbacks.foreach (function (callback) {handle (callback); }); }, 0); } fn (resolve);}
We combine 例4
the code, analyze the above code logic, in order to facilitate reading, I put 例4
the code here:
// 例4getUserId() .then(getUserJobById) .then(function (job) { // 对job的处理 });function getUserJobById(id) { return new Promise(function (resolve) { http.get(baseUrl + id, function(job) { resolve(job); }); });}
then
method, a new instance is created and returned Promise
, which is the basis of the serial and Promise
supports chaining calls.
handle
The method is an promise
internal method. then
method is passed to the onFulfilled
Promise
resolve
push
current promise
callbacks
queue, which is the link between the current promise
and the rear neighbor promise
, as well as when a new instance is created. The key to the problem (here must be a good analysis of the role of handle).
getUserId
The resulting promise
(short getUserId promise
) asynchronous operation succeeds, executes its internal method resolve
, and the passed parameter is the result of the asynchronous operationid
- Call the
handle
method callbacks
to process the callback in the queue: getUserJobById
method, generate a new promise
( getUserJobById promise
)
- Executes the
getUserId promise
then
new promise
(called) method that was generated by the method before the passed-in bridge promise
resolve
parameter is getUserJobById promise
. In this case, the method is resolve
passed in to the method getUserJobById promise
and is then
returned directly.
getUserJobById promise
when the asynchronous operation succeeds, execute the callbacks
callback in it: getUserId bridge promise
the resolve
method in
- The callback in the last execution
getUserId bridge promise
of the next neighbor promise
callbacks
.
More straightforward can look at the following figure, a picture of thousands of words (are based on their own understanding of the painting, if not welcome correct):
Failure handling
When an asynchronous operation fails, mark its status as rejected
, and execute the registered failure callback:
//例5function getUserId() { return new Promise(function(resolve) { //异步请求 http.get(url, function(error, results) { if (error) { reject(error); } resolve(results.id) }) })}getUserId().then(function(id) { //一些处理}, function(error) { console.log(error)})
With the fulfilled
experience of previous processing states, it is easy to support error handling by adding new logic to the registration callback and processing state changes:
function Promise (FN) {var state = ' pending ', value = null, callbacks = []; This.then = function (onfulfilled, onrejected) {return new Promise (function (resolve, reject) {handle ( {onfulfilled:onfulfilled | | null, onrejected:onrejected | | null, resolve: Resolve, reject:reject}); }); }; function handle (callback) {if (state = = = ' pending ') {Callbacks.push (callback); Return } var cb = state = = = ' fulfilled '? callback.onFulfilled:callback.onRejected, ret; if (cb = = = NULL) {cb = state = = = ' fulfilled '? Callback.resolve:callback.reject; CB (value); Return } RET = CB (value); Callback.resolve (ret); } function Resolve (NewValue) {if (NewValue && (typeof newvalue = = = ' object ' | | typeof newvalue = = = ' Fun Ction ')) {var thEn = Newvalue.then; if (typeof then = = = ' function ') {Then.call (newvalue, resolve, reject); Return }} state = ' fulfilled '; value = newvalue; Execute (); } function reject (reason) {state = ' rejected '; value = reason; Execute (); } function Execute () {setTimeout (function () {Callbacks.foreach (callback) { Handle (callback); }); }, 0); } fn (resolve, reject);}
The code above adds a new reject
method that is called when an asynchronous operation fails, while extracting resolve
and reject
sharing the part to form a execute
method.
Error bubbling is a feature that has been supported by the above code and is very useful. handle
when a callback that does not specify an asynchronous operation fails is found, it is bridge promise
set directly (the back then
of the function returned promise
) to the rejected
state, so that the effect of subsequent failed callbacks is achieved. This facilitates the Promise
cost of serial failure processing because a set of asynchronous operations often corresponds to an actual function, and the failure handling method is usually consistent:
//例6getUserId() .then(getUserJobById) .then(function (job) { // 处理job }, function (error) { // getUserId或者getUerJobById时出现的错误 console.log(error); });
Exception handling
A careful classmate will think: What if the code goes wrong when the successful callback and the failed callback are executed? For such exceptions, you can use the try-catch
catch error and bridge promise
set the rejected
state. The handle
method is modified as follows:
function handle(callback) { if (state === 'pending') { callbacks.push(callback); return; } var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected, ret; if (cb === null) { cb = state === 'fulfilled' ? callback.resolve : callback.reject; cb(value); return; } try { ret = cb(value); callback.resolve(ret); } catch (e) { callback.reject(e); } }
If you are executing multiple times in an asynchronous operation, resolve
or if reject
subsequent callbacks are processed repeatedly, you can resolve them by using a flag bit built in.
Summarize
At the beginning of the promise source code is not a good understanding of the then and resolve function of the operating mechanism, but if you calm down, in turn, according to the implementation of promise logic to deduce, it is not difficult to understand. Here must pay attention to the point is: Promise inside the then function is only to register the subsequent need to execute the code, the real execution is executed in the Resolve method, clear this layer, and then to analyze the source code will be more labor-saving.
Now review the implementation of promise, which mainly uses the observer pattern in the design pattern:
- The Observer method is registered with the Observer Promise object through the Promise.prototype.then and Promise.prototype.catch methods, and a new promise object is returned so that it can be called in a chain.
- The Observer manages the state transitions of the internal pending, fulfilled, and rejected, while actively triggering state transitions and notifying observers through the resolve and reject methods passed in the constructor.
Reference documents
Deep understanding of Promise
JavaScript Promises ... In Wicked Detail
This article from the NetEase cloud community, by the author Gu Jing authorized release.