Promise is excellent, so it is so useful that I think we should take a good look at Promise, or even implement a simple version. Before implementation, let's take a look at the use of Promise:
Use Promise
Callback hell
The first purpose of Promise is to effectively solve the problem of callback black hole. Assume that you want to implement a user-presented task. This task is divided into three steps:
Obtain user information
Get user images
Pop-up prompt
Without Promise, our implementation may be like this:
GetUserInfo (id, function (info ){
GetUserImage (info. img, function (){
ShowTip ();
})
})
Here is only three steps. If there is a longer string of tasks, we will fall into the callback black hole. To solve this problem, we can use Promise to process this long string of tasks, the Promise version is like this:
// GetUserInfo returns promise
GetUserInfo (id)
. Then (getUserImage)
. Then (showTip)
. Catch (function (e ){
Console. log (e );
});
The code that evolved to the right begins to develop downward, which is more suitable for programming habits. If we want to make our code more robust, we need to process error information at every step, after promise is used, we only need to deal with the problem in the final catch.
Concurrency
If we want to display 10 records on a page, but we only have one interface for getting records through id, we need to send 10 requests, after all the requests are completed, add all the records to the page. Promise is particularly suitable for use in this scenario.
The code may be like this:
// Ids: id of all records for which information is to be obtained
// GetRecordById interface for getting records, returns promise
Promise. all (ids. map (getRecordById ))
. Then (showRecords)
. Catch (function (e ){
Console. log (e );
});
This is some simple use of Promise. Of course, it is excited that Promise is already ES6 standard, and many browsers have already supported Promise native. For browsers that cannot use Promise, we can only implement it by ourselves. Let's take a look at the simple implementation of Promise.
Implementation
Warm up
Let's take a look at the MDN image and warm up to see if Promise's state migration: promise Promise has three states:
Pending: initial state, not fulfilled or rejected
Fulfilled: successful operation
Rejected: failed operation
We can see that the newly created Promise is in pending state. After fulfill is executed, the callback function of then will be called. If reject is enabled, catch will be called for exception handling, and a new promise will be returned whether then or catch is called, which is why promise can be chained.
Next, let's look at how the specification describes promise. Only the core part is extracted here, and the boundary issue is not considered.
Constructor: Promise (executor)
Check parameters: for example, whether executor is a function.
Initialization: [[State] = pending, [[FulfillReactions] = [], [RejectReactions] = []
Create a resolve object: {[[Resolve]: resolve, [[Reject]: reject}
Execute executor: executor (resolve, reject)
Therefore, the excuter passed in the constructor is immediately executed. FulfillReactions stores the operations performed when promise is successfully executed, and RejectReactions stores promise as the operation to be executed.
Function Promise (resolver ){
This. _ id = counter ++;
This. _ state = PENDING;
This. _ result = undefined;
This. _ subscribers = [];
Var promise = this;
If (noop! = Resolver ){
Try {
Resolver (function (value ){
Resolve (promise, value );
}, Function (reason ){
Reject (promise, reason );
});
} Catch (e ){
Reject (promise, e );
}
}
}
FulfillPromise (promise, value)
Check [[state], which must be pending (not pending indicates that it has been resolved and cannot be resolved repeatedly)
Value assignment: [[Result] = value, [[state] = fulfilled
Trigger the [[FulfillReactions] operation
ResolvePromise is the most closely related to FulfillPromise. Here we provide the implementation of ResolvePromise, but the difference is that Promise is parsed more directly.
Function resolve (promise, value ){
// Promise is returned for the resolve (promise is returned for the then callback)
If (typeof value = 'object'
& Promise. constructor === value. constructor ){
HandleOwnThenable (promise, value );
}
// The value for resolve is
Else {
If (promise. _ state! = PENDING) {return ;}
Promise. _ result = value;
Promise. _ state = FULFILLED;
Asap (publish, promise );
}
}
Function handleOwnThenable (promise, thenable ){
// If the returned promise has been completed
// Directly use the value of this promise to resolve the parent promise
If (thenable. _ state === FULFILLED ){
Resolve (promise, thenable. _ result );
} Else if (thenable. _ state === REJECTED ){
Reject (promise, thenable. _ result );
}
// If the returned promise is not completed
// Wait until the promise is completed before resolve parent promise
Else {
Subscribe (thenable, undefined, function (value ){
Resolve (promise, value );
}, Function (reason ){
Reject (promise, reason );
});
}
}
RejectPromise (promise, reason)
Check [[state], which must be pending (not pending indicates that it has been resolved and cannot be resolved repeatedly)
Value assignment: [[Result] = reason, [[state] = rejected
Trigger the [[RejectReactions] operation
To trigger [[FulfillReactions] and [[RejectReactions] is to traverse the array and execute all the callback functions.
Function reject (promise, reason ){
If (promise. _ state! = PENDING) {return ;}
Promise. _ state = REJECTED;
Promise. _ result = reason;
Asap (publish, promise );
}
Promise. prototype. then (onFullfilled, onRejected)
Promise = this
Create a resultCapability triple, {[[Promise], [[Resolve], [[Reject]} ([Promise)
FulfillReaction = {[[Capabilities]: resultCapability, [[Handler]: onFulfilled}
RejectReaction = {[Capabilities]: resultCapability, [[Handler]: onRejected}
If [[state] is pending: fulfillReaction, add [[FulfillReactions], add rejectReaction to [[RejectReactions]
If [[state] is fulfilled: fulfillReaction, add it to the execution queue.
If [[state] is rejected: rejectReaction, add it to the execution queue.
Returns resultCapability. [[Promise]
Here we can see that the constructor is closely related to then. If the newly created promise is an asynchronous operation, the state is pending, and the subpromise will be created when then is called, add the callback operation to the array of [[FulfillReactions] or [RejectReactions] of the parent promise. This is actually the publishing and subscription mode.
They have the following relationship: the relationship between the promise constructor and then
Both new promise and then or catch will get a new promise, which will subscribe to the completion events of the parent promise, after the parent-level promise is complete, a series of callback operations will be executed, that is, the release.
Promise. prototype. catch (onRejected)
Then syntax sugar: then (null, onRejected)
The following is the prototype of Promise:
Promise. prototype = {
Constructor: Promise,
Then: function (onFulfillment, onRejection ){
Var parent = this;
Var state = parent. _ state;
If (state === FULFILLED &&! OnFulfillment
| State === REJECTED &&! OnRejection ){
Return this;
}
Var child = new Promise (noop );
Var result = parent. _ result;
If (state ){
Var callback = arguments [state-1];
Asap (function (){
InvokeCallback (state, child, callback, result );
});
} Else {
Subscribe (parent, child, onFulfillment, onRejection );
}
Return child;
},
'Catch ': function (onRejection ){
Return this. then (null, onRejection );
}
};
Promise. resolve (value)
Create promise
Call ResolvePromise (promise, value) (not listed, will judge some situations and then call FulfillPromise)
Return promise
Promise. resolve = function (arg ){
Var child = new Promise (noop );
Resolve (child, arg );
Return child;
};
Promise. reject (value)
Create promise
Call RejectPromise (promise, value)
Return promise
Promise. reject = function (reason ){
Var child = new Promise (noop );
Reject (child, reason );
Return child;
};
Promise. all (iterator)
Now we can implement the basic promise, Promise. all and Promise. race does not continue to describe. If you are interested, you can continue to read the specifications. The figure above demonstrates my understanding of these two functions:
Promise. all understanding
Call promise. all creates an object to store the processing status of all promise and save the execution result. When remain is 0, you can resolve the newly created promise so that you can continue to execute it later.
Promise. all = function (promises ){
Var child = new Promise (noop );
Var record = {
Remain: promises. length,
Values: []
};
Promises. forEach (function (promise, I ){
If (promise. _ state === PENDING ){
Subscribe (promise, undefined, onFulfilled (I), onRejected );
} Else if (promise. _ state === REJECTED ){
Reject (child, promise. _ result );
Return false;
} Else {
-- Record. remain;
Record. values [I] = promise. _ result;
If (record. remain = 0 ){
Resolve (child, values );
}
}
});
Return child;
Function onFulfilled (I ){
Return function (val ){
-- Record. remain;
Record. values [I] = val;
If (record. remian = 0 ){
Resolve (child, record. values );
}
}
}
Function onRejected (reason ){
Reject (child, reason );
}
};
Promise. race (iterator)
Promise. race is similar to promise. all, but if one promise is complete, we can resolve the new promise.
Promise. race = function (promises ){
Var child = new Promise (noop );
Promises. forEach (function (promise, I ){
If (promise. _ state === PENDING ){
Subscribe (promise, undefined, onFulfilled, onRejected );
} Else if (promise. _ state === REJECTED ){
Reject (child, promise. _ result );
Return false;
} Else {
Resolve (child, promise. _ result );
Return false;
}
});
Return child;
Function onFulfilled (val ){
Resolve (child, val );
}
Function onRejected (reason ){
Reject (child, reason );
}
};
This is the basic content of promise. Please stamp the complete code here.
Other problems
Promises penetration
If the parameter passed in then is not a function, it will not be ignored. This is the reason for promise penetration, so the function will always be passed in then. The answer can be found in invokeCallback, a key function called in the then method:
Function invokeCallback (settled, promise, callback, detail ){
Var hasCallback = (typeof callback = 'function '),
Value, error, succeeded, failed;
If (hasCallback ){
Try {
Value = callback (detail );
} Catch (e ){
Value = {
Error: e
};
}
If (value &&!! Value. error ){
Failed = true;
Error = value. error;
Value = null;
} Else {
Succeeded = true;
}
}
// The then parameter is not a function
// Will be ignored, that is, promise penetration
Else {
Value = detail;
Succeeded = true;
}
If (promise. _ state === PENDING ){
If (hasCallback & succeeded
| Settled === FULFILLED ){
Resolve (promise, value );
} Else if (failed | settled === REJECTED ){
Reject (promise, error );
}
}
}
For example, in the following example, all results are output foo:
Promise. resolve ('Foo'). then (Promise. resolve ('bar'). then (function (result ){
Console. log (result );
});
Promise. resolve ('Foo'). then (null). then (function (result ){
Console. log (result );
});
Embrace the pyramid
Promise can well solve the pyramid problem, but sometimes we also need to use the pyramid as appropriate. For example, we want to get the results of two promise at the same time, but these two promise are associated, that is, there is order. What should I do?
The solution may be like this: define a global variable so that two promise results can be used in the second then.
Var user;
GetUserByName ('nolan'). then (function (result ){
User = result;
Return getUserAccountById (user. id );
}). Then (function (userAccount ){
// Okay. "user" and "userAccount" are available.
});
However, this is not the best solution. Why not abandon your prejudice and embrace the pyramid:
GetUserByName ('nolan'). then (function (user ){
Return getUserAccountById (user. id). then (function (userAccount ){
// Okay. "user" and "userAccount" are available.
});
});
Promise is so powerful and hard to understand, but it is not as complicated as I think after grasping the essence. This is why I want to write this article. More about how to use promise correctly