Example:
let a = Promise.resolve("123")
.then(val => {
console.log(a);
return 100;
})
.then(val => {
console.log(val);
console.log(a);
})
.finally(() => {
console.log(a);
});
Output:
Promise { <pending> }
100
Promise { <pending> }
Promise { <pending> }
Why a resolved promise still in pending state?
So, a contains the result of executing all of this:
let a = Promise.resolve(...).then(...).then(...).finally(...);
Each step of that phase creates a new promise and a contains the last one in the chain (the last promise is the one that .finally() returns). Each time you log a, you're logging the final promise and is has not yet been resolved until the chain is done.
Keep in mind that all these .then() and .finally() methods execute immediately and each returns a new promise. When they execute, they register the callbacks passed to them in a way that those callbacks can be called later when the promise they are associated with resolves or rejects. Even though the first promise is immediately resolved, it still doesn't execute it's .then() handlers until after this whole chain of .then().then().finally() executes and all the promise in that chain are created and all the handlers specified in that chain are registered. So, a contains the fourth promise that .finally() returns, not the promise that Promise.resolve() creates and that fourth promise is not resolved until after all the handlers in your chain have finished executing. Since you're doing the logging from all those handlers, that's why that last promise in the chain still shows as pending everywhere you're logging it.
If you do things differently like this:
let d = Promise.resolve("123");
d.then(val => {
console.log(d);
return 100;
}).then(val => {
console.log(val);
console.log(d);
}).finally(() => {
console.log(d);
});
Then, you will see this:
Promise { '123' }
100
Promise { '123' }
Promise { '123' }
Because now d represents the first promise in the chain, not the end promise in the chain so it is indeed resolved when all the other handlers execute.
So, probably the operative part here that you were not aware of is that each .then(), .catch() or .finally() returns a new promise that is hooked to the chain in front of it. Your original console.log(a) was monitoring only the very last promise in the chain, not the first promise in the chain or not the current promise in the chain.
Related
Say I have a function that returns a resolved promise like this:
let a = () => {return new Promise(res => res(1))}
and then I then-ify it like this:
a()
.then(val => {return new Promise(res => res(1))})
Here the then contains a handler that returns a promise resolved with 1, so the then block returns a promise resolved with 1 as well. Is that right?
Then say instead we have this:
a()
.then(val => {return 1})
The handler returns 1 instead of returning a promise resolved with 1.
What I Want To Know: Does the then block return a promise resolved with 1 in both of these scenarios, even though one handler returned a resolved promise and the other just returned a value? In other words, does a then block deal with handlers that return promises resolved with a value the same way they deal with handlers that return the value itself?
All values returned from a then block are implicitly wrapped in a Promise.resolve, so returning Promise.resolve(1) is unnecessary.
I think the short version of the question is: if the fulfillment handler supplied to a .then() returns a new promise, how does this new promise "unwrap" to the promise returned by .then()? (unwrap seems like a terminology used in the promise technology). (and if it is not returning a new promise but just a "thenable", how does it unwrap?)
To chain several time-consuming asynchronous promises together, such as doing several network fetches described on this page or a series of animations, I think the standard method is stated as:
In the fulfillment handler passed to .then(), create and return a new promise p, and in the executor passed to the constructor that created p, do the time-consuming task, and resolve this new promise p when done.
Sometimes I may take this as: this is just the way it is (how to chain time-consuming promises), or it is the language feature, and you can just considered it to be happening by magic (if it is a language feature).
But is it true that this is done by standard procedure how it would be handled if the fulfillment handler returns a new promise?
I wrote out some rough draft of how it could be done below. And to state it in a few sentences, it is
p1 is the first promise.
then() returns a new promise p2
the then() remembers what p2 is, as an internal property of p1.
when the fulfillment handler passed to then eventually get invoked (when p1 is resolved), the returned value is examined. If it is an object that has a property named then and is a function, then this object is treated as a thenable, which means it can be a genuine promise or just a dummy thenable (let's call it p1_New).
Immediately, this is invoked: p1_New.then(() => { resolveP2() })
Let's say p1_New is a genuine promise. When we resolve p1_New, it will resolve p2, and p2 will perform its "then" fulfillment handler, and the series of time-consuming promises can go on.
So this is the rough draft of code:
let p1 = new Promise(function(resolve, reject) {
// does something that took 10 seconds
resolve(someValue);
});
After the above code, the state of p1 is:
p1 = {
__internal__resolved_value: undefined,
__internal__state: "PENDING",
__internal__executor: function() {
// this is the executor passed to the constructor Promise().
// Something is running and the resolve(v) statement probably
// will do
// this.__internal__onResolve(v)
},
__internal__onResolve: function(resolvedValue) {
if (this.__internal__then_fulfillHandler) { // if it exists
let vFulfill = this.__internal__then_fulfillHandler(this.__internal__resolved_value);
// if the fulfillment handler returns a promise (a thenable),
// then immediately set this promise.then(fn1)
// where fn1 is to call this.__internal__resolveNewPromise()
// so as to resolve the promise newPromise that I am returning
if (vFulfill && typeof vFulfill.then === "function") { // a promise, or thenable
vFulfill.then(function() {
this.__internal__resolveNewPromise(this.__internal__resolved_value)
})
}
}
},
// in reality, the then should maintain an array of fulfillmentHandler
// because `then` can be called multiple times
then: function(fulfillmentHandler, rejectionHandler) {
this.__internal__then_fulfillHandler = fulfillmentHandler;
// create a new promise newPromise to return in any case
let newPromise = new Promise(function(resolve, reject) {
this.__internal__resolveNewPromise = resolve;
});
// if promise already resolved, then call the onResolve
// to do what is supposed to be done whenever this promise resolves
if (this.__internal__state === "RESOLVED") {
this.__internal__onResolve(this.__internal__resolved_value);
}
return newPromise;
}
}
and that's how when p1_New is resolved, then p2 is resolved, and the fulfillment handler passed to p2.then() will go on.
let p2 = p1.then(function() {
p1_New = new Promise(function(resolve, reject) {
// does something that took 20 seconds
resolve(someValue);
});
return p1_New;
});
p2.then(fulfillmentHandler, rejectionHandler);
But what if p1_New is not really a promise, but just a dummy thenable written this way:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after the 1 second
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // shows 2 after 1000ms
it is doing the time-consuming task in its then(). That's fine too: if we look at step 5 above:
Immediately, this is invoked: p1_New.then(() => { resolveP2() })
that is, then is invoked immediately, and with the first function passed in being able to resolve p2. So that thenable performs some time-consuming task, and when done, call its first parameter (the function that resolves p2), and the whole sequence can go on like before.
But in this case, it is the then() doing the time-consuming task, not the executor doing it (the executor passed to the constructor of a new promise). In a way, it is like the then() has become the executor.
I understand promises more now, it seems this is the standard way.
Whatever the fulfillment handler returns: a primitive value, an object that is not a thenable, a thenable that is like a fake promise, or a real promise, will all be set up so that it will resolve p, where p is the promise that is returned by then.
This is the standard procedure, and what is called unwrapping. We can wrap and wrap a promise by many layers, when it unwraps from the inside, it will unwrap until it cannot continue.
And it is true, when an thenable fake promise, or a real promise is returned, it will trigger this action to happen underneath:
obj.then(resolveP) where resolveP is the resolve function that can resolve p.
That how that promise resolves p, and how the thenable can do asynchronous task and use the first argument passed to it as a resolve function to "resolve" p.
I'd like to have a promise to a promise, something like this:
let first = new Promise(resolveFirst => {
setTimeout(() => {
resolveFirst("resolved!");
}, 2000)
});
let second = new Promise(resolveSecond => {
setTimeout(() => {
resolveSecond(first);
}, 10)
});
second.then(secondValue => {
console.log("second value: ", secondValue);
secondValue.then(firstValue => {
console.log("first value: ", firstValue);
});
});
So that the console.log("second value: ", secondValue); is printed after 10 millies and then the console.log("first value: ", firstValue) will be printed 2000 afterwards.
What happens though is that I get:
second value: resolved!
And an error:
Uncaught (in promise) TypeError: secondValue.then is not a function
Together after 2010 millies.
It seems that when the second promise is resolved, and the first one is returned then it automatically waits for this first promise to resolve as well.
Why is that? And how do I break between them?
Edit
Here's a solution that was posted on facebook using Array.reduce():
const runPromisesInSeries =
ps => ps.reduce((p, next) =>
p.then(next), Promise.resolve());
const delay = d => new Promise(r => setTimeout(r, d));
runPromisesInSeries([() => delay(1000), () => delay(2000)]);
//executes each promise sequentially, taking a total of 3 seconds to complete
(https://www.facebook.com/addyosmaniofficial/photos/a.10151435123819601.1073741825.129712729600/10155386805609601/?type=3&theater)
By design, a promise that is resolved with a second promise takes on the value and state of the 2nd promise after the 2nd promise becomes settled (fulfilled or rejected), waiting for it to be settled as necessary.
It is not possible to fulfill a promise with a promise or thenable object (any object with a then method`) value unless the promise library is seriously flawed (older versions of JQuery would fulfill their own promise objects with a promise object from a different library).
Promise rejection does not have the same checks in place and will happily pass a promise object down the chain without waiting for it to be resolved. So you could reject the 2nd promise with the first and pick it up in a catch clause.
Although technically feasible I strongly advise against doing so except to prove it can be done - it's a maintenance issue for a third party trying to understand the code.
While you could pass the promise as an object property down the success channel of a promise chain, re-analysis of promise composition may provide a better or more standard solution. E.G. Promise.all waits for for two or more independent promises to be fulfilled before proceeding on a common task.
That's just part of their charm: If your promise A resolves to a thenable B, then A resolves only after B resolves, and A takes B's resolved value. That's part of both Promises/A+ and ES6, as part of the "promise resolution procedure".
You can see some of the advantages of that if (say) one action requires an action before its completion (like login), or requires loading another page of results, or has retry logic of its own.
Though it's not very idiomatic, if you want to return an unresolved promise immediately without waiting for it, you can do so by passing it in an {object} or [array], but there might not be much reason for that: Other than waiting for its completion, what would you do with a returned promise?
You can chain then only to a promise.
secondValue is not a promise, it's just the value that returned when you called the second promise. or the resolved value from that promise.
If you want that to work, try this:
let first = new Promise(resolveFirst => {
setTimeout(() => {
resolveFirst("resolved!");
}, 2000)
});
let second = new Promise(resolveSecond => {
setTimeout(() => {
resolveSecond(first);
}, 10)
});
second.then(secondValue => {
console.log("second value: ", secondValue);
/** first is a Promise. return a promise, and the next "then"
* will pass the result of that Promise.
*/
return first
})
.then(firstValue => {
console.log(firstValue)
})
EDIT 1
Further explanation for why second promise resolved the eventual value (string) of the first promise, and not the promise itself, can be found on mdn:
Promise.resolve()
if the value was a promise, that object becomes the result of the call to Promise.resolve; otherwise the returned promise will be fulfilled with the value.
This question already has answers here:
How angular promise .then works
(2 answers)
Closed 7 years ago.
All:
I am pretty new to Promise, just curious how they get resolved, one thing confuse me is:
Some posts show using
var defer = $q.defer();
// some logic block
{
// if success doing something
defer.resolve();
}
return defer.promise;
But if use .then() function, promise is returned from .then(function(){}), I wonder how do I control if this promise resolved or not?
Another confuse is: If I use some chained .then() function, I wonder what is the relationship between them, are they same promise object which is just passed down or each .then will generate a new Promise object and return it?
As specified in this throughout and clear document:
QUESTION 1. I wonder how do I control if this promise resolved or not?
One of the Promise APIs support pecial functions that resolve() or reject() a Promise. So you may use the following functions in your code
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
Rejections happen when a promise is explicitly rejected, but also implicitly
if an error is thrown in the constructor callback.
var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse throws an error if you feed it some
// invalid JSON, so this implicitly rejects:
resolve(JSON.parse("This ain't JSON"));
});
jsonPromise.then(function(data) {
// This never happens:
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
});
In other variants the Promise is resolved with the return value that is passed to the next link in the chain.
QUESTION 2.
Promises are in some sense functions that will result in the future with some value. The result value is the return value from promise - so basically promise chaining ( .then(...).then... ) are chain of functions that wait till the previous one will end ( resolve with some value ). Then they are called with an argument which is the return value of the last executed function in the queue ( previous link in the chain ).
.then() returns a new promise object thus allowing chaining. (see remark for documentation link)
REMARK
There is great and small description of all Angular promises in official documentation under the section Promise API and next one - Chaining the promises.
This isn't an attempt to explain promises in their full glory - there are blogs for that. This is to answer your specific questions:
Q1:
But if I use .then() function, promise is returned from .then(function(){}), I wonder how do I control if this promise resolved or not?
The resolve handler function of .then controls how this promise is resolved:
If the handler function returns a non-promise value, then the promise with resolve with that value.
var thenPromise = originalPromise.then(function success() {
return "foo";
});
thenPromise.then(function(data){
console.log(data); // "foo"
});
If the handler function returns another promise, then the .then promise will resolve exactly how the new promise would resolve (or reject)
var thenPromise = originalPromise.then(function() {
return $timeout(function(){ return "foo"; }, 1000);
});
thenPromise.then(function(data){
console.log(data); // (after 1 second) "foo"
});
If the handler function throws an exception or if the the return is an explicitly rejected promise `$q.reject:
var thenPromise = originalPromise.then(function() {
return $q.reject("some error");
});
thenPromise.then(function(data){
console.log(data); // doesn't get here
})
.catch(function(err){
console.log(err); // "some error"
});
Q2:
If I use some chained .then() function, I wonder what is the relationship between them, are they same promise object which is just passed down or each .then will generate a new Promise object and return it?
Each .then generates its own promise.
var timeoutPromise = $timeout(afterTimeout, 1000);
var thenPromise = timeoutPromise.then(doSomething);
var anotherThenPromise = timeoutPromise.then(doSomethingElse);
If timeoutPromise resolves, then both doSomething and doSomethingElse would execute and depending on their outcome thenPromise and anotherThenPromise would have their respective resolutions.
According to the Promise A+ spec,
2.2.2.1 If onFulfilled is a function, it must be called after promise is fulfilled, with promise’s value as its first argument.
However, what happens if a promise is passed in .then()?
Now we have this code:
var Promise = require('bluebird');
var func1 = function() {
return new Promise(function(resolve, reject) {
resolve('hello');
});
}
var wrapper = function() {
return func1();
}
var api = function() {
return wrapper()
.then(wrapper());
}
api().then(function(msg) {
console.log(msg);
});
Why is 'hello' get printed?
In the api function, wrapper() is evaluated as a promise through func1() and get passed in .then(). Right now, this promise acts 'onFulfilled' function according to the spec quoted. However, the spec says the onFulfilled(the returned promise in this case) is expecting a parameter passed in as the value of the previous promise calling .then(). But how can the value get passed into a promise?
I am not very good at explaining things. So if you are confused, can you explain why 'hello' is printed? How?
But how can the value get passed into a promise?
It can't and the part of the spec you quoted should have already told you that:
If onFulfilled is a function [...]
a promise is a not a function! (usually, and if it were, it would act like a function, not like a promise).
So calling .then basically doesn't do anything:
2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
promise1 is fulfilled with "hello", so that's what promise2 to is fulfilled with as well.
wrapper()/*promise 1*/.then(wrapper())/*promise 2*/.then(function(msg) {
console.log(msg);
});
is essentially
wrapper()/*promise 1*/.then(function(msg) {
console.log(msg);
});
and that's why "hello" is printed.
Now, if you passed wrapper, a function, instead, i.e. wrapper().then(wrapper).then(...), which is the same as
wrapper()/*a*/.then(function foo(result) {
return func1(); /*b*/
})/*c*/.then(function(msg) {
// ...
});
then foo would get the result from the first promise (a) (according to the part your quoted). And since the onFulfilled function returns a promise itself (b), the promise returned by .then (c) is resolved with value of that inner promise (b) (which happens to produce the same value as the first initial promise (a)).
This is described in section 2.3, where x is the value that is returned by onFulfilled.
If x is a promise, adopt its state:
If x is pending, promise must remain pending until x is fulfilled or rejected.
If/when x is fulfilled, fulfill promise with the same value.
If/when x is rejected, reject promise with the same reason.