I want to fulfill a promise with some other promise. The point is that I really want to get access to the (still pending) second promise as soon as the first promise is fulfilled. Unfortunately, I only seem to be able to get the second promise's resolution value once both both promises are fulfilled.
Here's the use case that I have in mind:
var picker = pickFile();
picker.then( // Wait for the user to pick a file.
function(downloadProgress) {
// The user picked a file. The file may not be available just yet (e.g.,
// if it has to be downloaded over the network) but we can already ask
// the user some more questions while the file is being obtained in the
// background.
...do some more user interaction...
return downloadProgress;
}
).then( // Wait for the download (if any) to complete.
function(file) {
// Do something with the file.
}
)
The function pickFile
displays a file picker where the user may pick a file either from their own hard drive or from a URL. It returns a promise picker
that is fulfilled as soon as the user has picked a file. At this point, we may still have to download the selected file over the network. Therefore, I cannot fulfill picker
with the selected file as resolution value. Instead, picker
should be fulfilled with another promise, downloadProgress
, which in turn will eventually be fulfilled with the selected file.
For completenes, here's a mock implementation of the pickFile
function:
function pickFile() {
...display the file picker...
var resolveP1 = null;
var p1 = new Promise(
function(resolve, reject) {
resolveP1 = resolve;
}
);
// Mock code to pretend the user picked a file
window.setTimeout(function() {
var p2 = Promise.resolve('thefile');
resolveP1(p2); // <--- PROBLEM: I actually want to *fulfill* p1 with p2
}, 3000);
return p1;
}
The problem in the marked line is that I would like to fulfill the promise p1
with the new promise p2
, but I only know how to resolve it. The difference between fulfilling and resolving is that resolving first checks if the supplied value p2
is again a promise. If it is, then fulfillment of p1
will be deferred until p2
is fulfilld, and then p1
will be fulfilled with p2
's resolution value instead of p2
itself.
I could work around this issue by building a wrapper around p2
, i.e. by replacing the line
resolveP1(p2); // <--- PROBLEM: I actually want to *fulfill* p1 with p2
from the second code example by
resolveP1({promise: p2});
Then, in the first code example, I'd have to replace the line
return downloadProgress;
by
return downloadProgress.promise;
But this seems like a bit of a hack when all I really want to do is just fulfill (instead of resolve) a promise.
I'd appreciate any suggestions.
There doesn't seem to be a solution apart from the workaround I already described in the question. For future reference, if you want to fulfill (rather than resolve) a promise
p
with a valueval
, whereval
is another promise, then just calling the promise resolution function forp
with argumentval
won't work as expected. It would causep
to be "locked in" on the state ofval
, such thatp
will be fulfilled withval
's resolution value onceval
is fulfilled (see spec).Instead, wrap
val
in another object and resolvep
with that object:This solution works if you already know that
val
is a promise. If you cannot make any assumptions aboutval
's type, then you seem to be out of luck. Either you have to always wrap promise resolution values in another object, or you can try to detect whetherval
has a fieldthen
of type "function" and wrap it conditionally.That said, in some cases the default behavior of promise resolution may actually have the desired effect. So only use the workaround described above if you are sure that you want to fulfill instead of resolve the first promise with the second one.
Found a similar solution in the process of moving away from Angular's $q to the native Promise feature.
Promise.all
could be an option (in cases of independent parallel async tasks) by passing around an appropriate object, or something decorated with the state, passing it off to whatever is ready when appropriate. In the Promise.all sample below note how it recovers in one of the promises--took me awhile to realize how to redirect the result of a chain. The result of the all is just the last promise's return. While this doesn't answer the question's title, usingreturn Promise.reject(<an-object-including-a-promise>)
(or resolve) gives a series and/or group of async tasks shared access and control along the way. In the case of picking, downloading then working with a file I'd take out the progress-event handling then do:pickFile.then(download,orFailGracefully)
withdownloadProgress
handled within thedownload
onResolve handler (download-progress doesn't appear to be an async task). Below are related experiments in the console.Although different people use different terms, in common terminology, "fulfill" means to put a promise in the "success" state (as opposed to "reject")--the state that will trigger then
then
handlers hanging off it.In other words, you cannot "fulfill" a promise with a promise. You can fulfill it with a value. (By the way, the term "resolve" is usually meant as either of fulfilling or rejecting.)
What you can do is return a promise from a
.then
handler and that will have the effect of essentially replacing the original promise with the returned promise.Here is a simple example of doing that:
where
asyncTask1
is a promise, andasyncTask2
is a function which returns a promise. So whenasyncTask1
is fulfilled (done successfully), thenasyncTask2
runs, and the promise returned by the.then
is "taken over" by the promiseasyncTask2
returns, so that when it finishes, the data can be processed.I can do something similar by calling
Promise.resolve
with a promise as parameter. It's a bit of a misnomer, because I'm not resolving the promise in the technical sense. Instead, the new promise created is "inhabited" by the promise I passed in. It's also useless, because using the result is exactly the same as using the promise I passed in:behaves exactly the same as
(assuming asyncTask2 is already a promise; otherwise
Promise.resolve
has the effect of creating a promise which is immediately fulfilled with the passed in value.)Just as you can pass a promise to
Promise.resolve
, you can pass a promise to theresolve
function provided to you as a parameter of the promise constructor callback. If the parameter you pass toresolve
is a non-promise, the promise immediately fulfills with that value. However, if the parameter you pass toresolve
is another promise, that promise "takes over the body" of the promise you are constructing. To put it another way, the promise you are constructing starts to behave exactly as the the promise passed toresolve
.By "behave exactly" I mean, if the promise you pass in to
resolve
is already fulfilled, the promise you are constructing is instantly fulfilled with the same value. If the promise you pass in toresolve
is already rejected, the promise you are constructing is instantly rejected with the same reason. If the promise you pass in toresolve
is not resolved yet, then anythen
handlers you hang off the promise you are constructing will be invoked if and when the promise you pass toresolve
is resolved.Just as it is confusing that
Promise.resolve
may result in a promise which is not actually resolved, it is similarly confusing that calling theresolve
function handed to you as a parameter to the promise constructor may not actually resolve the promise being constructed if you call it with an unresolved promise. Instead, as I've said a couple of times now, it has the effect of putting the promise being constructed in a state of total congruence with the promise passed toresolve
.Therefore, unless I am missing the point of your question,
pickfile
could be written asI didn't really understand your question clearly, so this might not be what you want. Please clarify if you care to.