Promise Core Description
Although promise has its own specifications, but the current various types of promise libraries, in the promise implementation of the details are different, and some of the API even in the sense of a completely distinct. But the core content of promise is interlinked, it is the then method. In relevant terms, promise refers to an object or function that has a then method that triggers a particular behavior.
Promise can have different implementations, so the promise core description does not discuss any specific implementation code.
Read the Promise core description first means: Look, this is the result that needs to be written out, please refer to this result and think about how to write it in code.
Getting Started: Understanding promise in this way
Think back to what promise is solving? Callback. For example, the function DoMission1 () represents the first thing, now we want to do the next thing after this thing is done DoMission2 (), what should we do?
Let's take a look at our common callback patterns. DoMission1 () said: "If you want to do so, give DoMission2 () to me, I will help you call after the end." "So it would be:
What about the Promise model? You say to DoMission1 (): "No, control is in my place." You should change, you first return a special thing to me, then I will use this thing to arrange the next thing. "This particular thing is promise, and it will become this:
DoMission1 (). then (DoMission2);
Can be seen, promise will callback mode of the master-slave relationship replaced a position (turn to be the owner!) , the process relationships of multiple events can be focused on the main path (rather than being scattered within the individual event functions).
Well, how do you do such a transformation? In the simplest case, suppose the code for DoMission1 () is:
function DoMission1 (callback) {
var value = 1;
callback (value);
}
Well, it can change, and it's like this:
function DoMission1 () {return
{
then:function (callback) {
var value = 1;
callback (value);
}
;
}
This completes the conversion. While it's not actually a useful conversion, it has actually touched on the most important implementation point of promise, that is, promise converts the return value to an object with a then method.
Advanced: Design distance of Q
starting from defer
Design/q0.js is the first step in the initial formation of Q. It creates a tool function called defer, which is used to create the promise:
var defer = function () {
var pending = [], value;
return {
resolve:function (_value) {
value = _value;
for (var i = 0, ii = pending.length i < II i++) {
var callback = pending[i];
callback (value);
}
pending = undefined;
},
Then:function (callback) {
if (pending) {
Pending.push (callback);
} else {
callback (value);}}}
;
The source can see that running defer () will get an object that contains two methods of resolve and then. Think back to the deferred of jquery (which also has resolve and then), and these two methods will be similar. Then will refer to the state of the pending and, if it is a wait state, save the callback (push) or call the callback immediately. Resolve will be sure of this promise, updating the value while running all saved callbacks. Examples of the use of defer are as follows:
var oneonesecondlater = function () {
var result = defer ();
settimeout (function () {
result.resolve (1);
}, 1000);
return result;
};
Oneonesecondlater (). then (callback);
Here Oneonesecondlater () contains asynchronous content (settimeout), but let it immediately return a defer () generated object and then place the object's resolve method at the end of the asynchronous call (with the value, or the result).
Here, there is a problem with the above code: Resolve can be executed multiple times. Therefore, resolve should be added to the state of the judgment, to ensure that resolve only once effective. This is the next design/q1.js of Q (Only the difference section):
Resolve:function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length i < II i++) {
var callback = pending[i];
callback (value);
}
pending = undefined;
} else {
throw new Error ("A promise can only be resolved once.");
}
For the second and more calls, you can throw an error or simply ignore it.
separation of defer and promise
In the previous implementation, the defer generated objects had both then methods and resolve methods. By definition, promise is concerned with the then approach, and the resolve that triggers promise to change state is another matter. So, Q next will have the then method of promise, and have resolve defer separate, separate use. This is like a clear delineation of their respective responsibilities, leaving only a certain amount of authority, which will make the code logic clearer, easy to adjust. Please see design/q3.js: (Q2 here skipped)
var ispromise = function (value) {return
value && typeof value.then = = ' function ';
};
var defer = function () {
var pending = [], value;
return {
resolve:function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length i < II i++) {
var callback = pending[i];
callback (value);
}
pending = undefined
}
,
promise: {
then:function (callback) {
if (pending)
{ Pending.push (callback);
} else {
callback (value);}}}};
If you compare Q1 carefully, you will find that the difference is very small. On the one hand, the error is no longer thrown (instead of ignoring the second and more resolve), on the other hand, the then method is moved to an object named Promise. Here, run defer (called defer bar), will have the resolve method, and a promise attribute to point to another object. The other object is a promise that has only then methods. This completes the separation.
There is also a ispromise () function that determines whether an object is a promise (duck-typing judgment method) by whether there is a then method. In order to properly use and process detached promise, it is necessary to distinguish promise from other values as such.
implementation of promise Cascade
The next step will be a very important one. To the front to Q3, the implementation of the promise are not cascaded. But the promise you are familiar with should support such a syntax:
Promise.then (STEP1). then (STEP2);
The above process can be understood to mean that promise will be able to create new promise and take the value from the old promise (value in the preceding code). To achieve then cascading, something needs to be done:
- The then method must return promise.
- The returned promise must set its own value using the returned result of the callback passed to the then method.
- The callback passed to the then method must return a promise or value.
In Design/q4.js, to achieve this, a new tool function ref:
var ref = function (value) {
if (value && typeof value.then = = "function") return
value;
return {
then:function (callback) {return
ref (callback (value));
}
};
This is the process of dealing with the value associated with the promise. This tool function will be packaged once for any value value and, if it is a promise, do nothing and, if not promise, wrap it into a promise. Note that there is a recursive, which ensures that the wrapped promise can be cascaded using the then method. To help understand it, here is an example of a use:
Ref ("Step1"). Then (function (value) {
console.log (value);//"Step1" return
;
}). Then (function (value) {
console.log (value);
You can see how value is passed, and so does the promise cascade.
Design/q4.js converts the original defer into a cascading form by using this ref function in combination:
var defer = function () {var pending = [], value;
return {resolve:function (_value) {if (pending) {value = ref (_value);//values wrapped in a promise
for (var i = 0, ii = pending.length i < II i++) {var callback = Pending[i]; Value.then (callback);
Then called instead} pending = undefined;
}, Promise: {then:function (_callback) {var result = defer (); Callback is wrapped so it return//value are captured and used to resolve the promise//that "th
En "returns var callback = function (value) {Result.resolve (_callback (value));
};
if (pending) {Pending.push (callback);
else {Value.then (callback);
return result.promise;
}
}
};
};
The original form of callback (value) is modified to Value.then (callback). The effect is actually the same as the original, just because value becomes the type of promise wrapper, it needs to be called.
Then method has a lot of changes, will first reborn into a defer, and at the end of the return of the defer promise. Note that callback is no longer the one that is passed directly to then, but adds a layer above it and places the newly generated defer resolve method here. As you can see here, the then method will return a newly generated promise, so you need to reserve the promise resolve, and the promise of the new resolve will run after the promise of the old resolve. This can be like a pipe, let the event according to the content of then connection, layer by layer pass down.
Add error handling
The then method of promise should be able to contain two parameters, respectively, the processing function of the positive and negative states (Onfulfilled and onrejected). The promise we have previously achieved can only be converted to a positive state, so the negative state section should be added next.
Note that the two parameters of the promise then method are optional parameters. Design/q6.js (Q5 also skips) joins the tool function reject to help implement the negative state of promise:
var reject = function (reason) {return
{
then:function (callback, Errback) {return
ref (Errback (reason));
}};
The main difference between it and ref is that the then method of the object it returns will only run with the errback of the second argument. The remainder of the design/q6.js is:
var defer = function () {var pending = [], value;
return {resolve:function (_value) {if (pending) {value = ref (_value);
for (var i = 0, ii = pending.length i < II i++) {value.then.apply (value, pending[i]);
} pending = undefined;
}, Promise: {then:function (_callback, _errback) {var result = defer (); Provide default callbacks and errbacks _callback = _callback | |
function (value) {//By default, forward fulfillment return value;
}; _errback = _errback | |
function (reason) {//By default, forward rejection return reject (reason);
};
var callback = function (value) {Result.resolve (_callback (value));
};
var errback = function (reason) {result.resolve (_errback (reason));
};
if (pending) {Pending.push ([callback, Errback]); } else {
Value.then (callback, Errback);
return result.promise;
}
}
};
};
The main change here is to save the array pending in the form of a single callback instead of both positive and negative callbacks. Furthermore, the default positive and negative callbacks are defined in then, which makes the then method meet the requirements of the Promise 2 optional parameters.
You may notice that there is only one resolve method in defer, and there is no reject like jquery. So how does error handling trigger? Take a look at this example:
var defer1 = defer (),
promise1 = defer1.promise;
Promise1.then (function (value) {
Console.log ("1:value =", value);
Return reject ("error happens");
Then (function (value) {
Console.log ("2:value =, value";
}). Then (null, function (reason) {
Console.log ("3:reason =", reason);
Defer1.resolve (ten);
Result:
//1:value = ten
//3:reason = error happens
As you can see, each return value passed to the then method is important, and it determines the result of the call to the next then method. If, as above, returning the object generated by the tool function reject, it triggers error handling.
into the asynchronous
Finally to the end of the design/q7.js. Until the front of the Q6, there is a problem, is that the then method may be synchronized, or asynchronous, depending on the function passed to the then (for example, directly return a value, that is, synchronization, return a different promise, can be asynchronous). This uncertainty may pose a potential problem. So the next step in Q is to make sure that all then are converted to asynchronous.
Design/q7.js defines another tool function Enqueue:
var enqueue = function (callback) {
//process.nexttick (callback);//Nodejs
settimeout (callback, 1);//Na?ve Bro Wser Solution
};
Obviously, this tool function defers any function to the next event queue.
Design/q7.js Other modification points are (show only the modifications):
var ref = function (value) {//... return {then:function (callback) {var
result = defer ();
XXX Enqueue (function () {Result.resolve (callback (value));
});
return result.promise;
}
};
};
var reject = function (reason) {return {then:function (callback, errback) {var result = defer ();
XXX Enqueue (function () {result.resolve (errback (reason));
});
return result.promise;
}
};
};
var defer = function () {var pending = [], value; return {resolve:function (_value) {//... enqueue (function () {value.then.apply value,
Pending[i]);
});
...}, Promise: {then:function (_callback, _errback) {//... enqueue (function () {
Value.then (callback, Errback);
});
// ...
}
}
};
};
That is, the original value.then of the part, are converted to asynchronous.
To this, Q provides the promise design principle q0~q7, all end.
Conclusion
Even though this article has been so long, it is only the basic promise of the story. Most promise libraries will have more APIs to deal with more and promise-related requirements, such as all (), spread (), but, read here, you've learned the core idea of achieving promise, which will help you to apply promise in the future.
In my opinion, promise is a delicate design, and it took me quite some time to understand it almost. Q as a typical promise library, in the way of thinking is very clear. Can feel, again complex library is to start from the basic point first, if we want to do similar things, also should maintain this mentality 1.1 points progress.