To gain better understanding of how promises work in Javascript I decided to give it a try and code basic implementation myself.
Basically I want to implement Promises Object (I call it Aaa in my code) that takes function as an argument. This function can call resolve to resolve
the promise, or reject to reject
it. The basic implementation and usage is below. Not sure if the second argument is accepteable according to promise specs, but that's what I got so far.
Aaa=function(f,pause) {
console.log("ggg");
var t=this;
this.f=f;
this.thens=[];
this.resolve=function(g) {
for(var i=0;i<t.thens.length;i++)
{
// try/catch to be used later for dealing with exceptions
try
{
t.thens[i].f(g);
t.thens[i].resolve();
}
catch(ex)
{}
}
};
// to be implemented later
this.reject=function(g) {};
this.then=function(resolve,reject) {
// i'm passing true for pause argument as we dont need to execute promise code just yet
var nextPromise=new Aaa(resolve,true);
this.thens.push(nextPromise);
return nextPromise;
}
if(!pause)
this.f(this.resolve,this.reject);
}
var aaa=new Aaa(function(resolve,reject) {
console.log("aaa");
setTimeout(function() {
console.log("fff");
resolve("good");
},2000);
console.log("bbb");
});
So now the promise can be created, called and resolved. Each then
method will return new Aaa (Promise) so these can be chained. Now the code below uses promise created above and chains then
callbacks. Each then
returns new promise and in this case it seems to work fine:
aaa.then(function(res) {
console.log("ccc");
console.log(res);
})
.then(function(res) {
console.log("ddd");
console.log(res);
},function(rej) {
console.log("eee");
console.log(rej);
});
the output I'm getting is:
ggg
aaa
bbb
ggg
ggg
fff
ccc
good
ddd
undefined
The problem is however when one of the then
calls returns a promise:
aaa.then(function(res) {
console.log("ccc");
console.log(res);
// here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that?
return new Aaa(function(resolve,reject) {
console.log("iii");
setTimeout(function() {
console.log("kkk");
resolve("good2");
// reject("bad");
},2000);
console.log("jjj");
}).then(function (res) {
console.log("lll");
console.log(res);
});
})
.then(function(res) {
console.log("ddd");
console.log(res);
},function(rej) {
console.log("eee");
console.log(rej);
});
The output is:
ggg
aaa
bbb
ggg
ggg
fff
ccc
good
ggg
iii
jjj
ggg
ddd
undefined
kkk
lll
good2
The call then where ddd
is output should not be called UNTIL the returned promise we just added is resolved.
How would that be best implemented?
(For a full Promise implementation, scroll down).
Some issues in your code
The are several issues, but I think the main mistake in your code is that you take the argument given to the
then
method and pass it as argument to a new promise:Although both arguments are call back functions, they have a different signature, and serve entirely different purposes:
then
method, is a call back function which will only get executed later, asynchronously, when the base promise is resolved, and to which the resolved value is passed as argument.You can see the difference also in your code, where you store the argument to the constructor as the f property. You have both this:
...where g is the resolved value, but also this:
...where the arguments are functions. When you create the nextPromise you will in fact first call f with these two arguments, and then later, with the g argument.
A Promises/A+ compliant implementation from the ground up
We could build our own Promise implementation by following the requirements in the Promises/A+ specification:
2.1 Promise states
There are only 2 state transitions allowed: from pending to fulfilled, and from pending to rejected. No other transition should be possible, and once a transition has been performed, the promise value (or rejection reason) should not change.
Here is a simple implementation that will adhere to the above restrictions. The comments reference the numbered requirements in the above specification:
Of course, this does not provide the
then
method, which is key to Promises:2.2 The
then
MethodThis is the core of the specification. The above code can be extended to expose the
then
method, which returns a promise and provides asynchronous execution of the appropriatethen
callback, only once, providing for multiplethen
calls, turning exceptions to rejections, ...etc.So the following code adds the
then
method, but also abroadcast
function which is defined separately, because it must be called on any state change: this does not only include the effect of thethen
method (a promise is added to a list), but also of theresolve
andreject
methods (state and value change).This covers almost everything, except that at the
TODO:
comment, the so-called Promise Resolution Procedure must be called:2.3 The Promise Resolution Procedure
This is a procedure that treats values that are thenables (or even promises) differently: instead of returning the value as-is, the procedure will execute the
then
method on that value and asynchronously fulfills the promise with the value received from thatthen
callback. It is not mentioned in the specs, but this is interesting to perform not only in thethen
method, but also when the main promise is resolved with such a value.So the existing
resolve
method should be replaced with this "Promise Resolution Procedure", which will call the original one. The original one could be called "fulfill", to indicate it will resolve the promise always as fulfilled:This is now Promises/A+ compliant, at least it passes the test-suite. Yet, the Promise object exposes far too many methods and properties:
A Promise Object with
then
onlyThe above built constructor creates something that is more like a Deferred object, i.e. which exposes
resolve
andreject
methods. Even worse, thestatus
andvalue
properties are writable. So, it would be more logical to regard this as a constructor for an unsecured Deferred object, and create a separate Promise constructor which builds on that, but only exposes what is needed: athen
method and a constructor callback that can accessresolve
andreject
.The deferred object can then do without the constructor callback argument, and provide access to the pure promise object via a
promise
property:There are several optimisations possible to this code, such as making Deferred methods private functions, and merging similar code into shorter code blocks, but as it stands now it shows quite clearly where each requirement is covered.
Happy coding.
There are a number of cases you're not handling here. The best bet is to start by building the promise as a state machine:
Now lets define a simple helper to use through the rest of our implementation:
Next, we need to consider each of the transformations that can occur:
Note how
resolve
can receive a Promise as its argument, but a Promise can never be fulfilled with another Promise. So we have to handle this special case.Note also that a Promise can only ever be fulfilled/rejected once. We also have the problem that a third party Promise may misbehave, and we should guard our code against that. For this reason, I haven't just called
result.then(resolve, reject)
from withinresolve
. Instead, I split that into a separate function:So now we have a completed state machine, but no way to observe or trigger the changes in state. Lets start by adding a way to trigger the state changes by passing in a resolver function.
As you can see, we re-use
doResolve
because we have another un-trusted resolver. Thefn
might callresolve
orreject
multiple times, and it might throw an error. We need to handle all of these cases (and that's whatdoResolve
does).We now have the completed state machine, but we haven't exposed any information about what state it is in. Lets try adding a
.done(onFulfilled, onRejected)
method that is just like.then
except that it does not return a Promise and does not handle errors thrown byonFulfilled
andonRejected
.Note how we must handle the case of
.done
being called both before and after the Promise becomes fulfilled/rejected.We almost have a complete promise implementation, but, as you already noticed when building your own implementation, we need a
.then
method that returns a Promise.We can build this easilly out of
.done
:Note here how we get the thing you were struggling with for free now, because
resolve
accepts a Promise and waits for it to be resolved.N.B. I haven't tested this Promise implementation (although it is correct to the best of my knowledge). You should test any implementation you build against the Promises/A+ test suite (https://github.com/promises-aplus/promises-tests) and may also find the Promises/A+ spec (https://github.com/promises-aplus/promises-spec) useful in figuring out what the correct behavior is for any specific part of the algorithm. As a final resource, promise is a very minimal implementation of the Promise spec.
this all seems extremely complicated. I think there's a really simple recursive solution. I'm going to omit the reject for the sake of brevity, but it's pretty much the same as resolve except you halt the chain.
my solution