Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fulfill (don't resolve) promise with another promise

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.

like image 429
robamler Avatar asked Aug 23 '15 15:08

robamler


People also ask

Can a Promise resolve another Promise?

"The point is that a promise will never resolve to another promise. It will always resolve to that promise's (eventual) value." Is jQuery $.

What happens if you don't resolve a Promise?

A promise is just an object with properties in Javascript. There's no magic to it. So failing to resolve or reject a promise just fails to ever change the state from "pending" to anything else. This doesn't cause any fundamental problem in Javascript because a promise is just a regular Javascript object.

How do you resolve a fulfilled Promise?

Promise resolve() method: If the value is a promise then promise is returned. If the value has a “then” attached to the promise, then the returned promise will follow that “then” to till the final state. The promise fulfilled with its value will be returned.

How do you reject Promise and resolve?

A Promise executor should call only one resolve or one reject . Once one state is changed (pending => fulfilled or pending => rejected), that's all. Any further calls to resolve or reject will be ignored.


1 Answers

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 value val, where val is another promise, then just calling the promise resolution function for p with argument val won't work as expected. It would cause p to be "locked in" on the state of val, such that p will be fulfilled with val's resolution value once val is fulfilled (see spec).

Instead, wrap val in another object and resolve p with that object:

var resolveP;  // Promise resolution function for p

var p = new Promise(
    function(resolve, reject) {
        resolveP = resolve;
    }
);

function fulfillPwithPromise(val) {  // Fulfills p with a promise val
    resolveP({promise: val});
}

p.then(function(res) {
    // Do something as soon as p is fulfilled...

    return res.promise;
}).then(function(res) {
    // Do something as soon as the second promise is fulfilled...
});

This solution works if you already know that val is a promise. If you cannot make any assumptions about val'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 whether val has a field then 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.

like image 139
robamler Avatar answered Sep 23 '22 02:09

robamler