Quantcast
Channel: How do I return the response from an asynchronous call? - Stack Overflow
Viewing all articles
Browse latest Browse all 46

Answer by T.J. Crowder for How do I return the response from an asynchronous call?

$
0
0

Most of the answers here give useful suggestions for when you have a single async operation, but sometimes, this comes up when you need to do an asynchronous operation for each entry in an array or other list-like structure. The temptation is to do this:

// WRONGvar results = [];theArray.forEach(function(entry) {    doSomethingAsync(entry, function(result) {        results.push(result);    });});console.log(results); // E.g., using them, returning them, etc.

Example:

// WRONGvar theArray = [1, 2, 3];var results = [];theArray.forEach(function(entry) {    doSomethingAsync(entry, function(result) {        results.push(result);    });});console.log("Results:", results); // E.g., using them, returning them, etc.function doSomethingAsync(value, callback) {    console.log("Starting async operation for "+ value);    setTimeout(function() {        console.log("Completing async operation for "+ value);        callback(value * 2);    }, Math.floor(Math.random() * 200));}
.as-console-wrapper { max-height: 100% !important; }

The reason that doesn't work is that the callbacks from doSomethingAsync haven't run yet by the time you're trying to use the results.

So, if you have an array (or list of some kind) and want to do async operations for each entry, you have two options: Do the operations in parallel (overlapping), or in series (one after another in sequence).

Parallel

You can start all of them and keep track of how many callbacks you're expecting, and then use the results when you've gotten that many callbacks:

var results = [];var expecting = theArray.length;theArray.forEach(function(entry, index) {    doSomethingAsync(entry, function(result) {        results[index] = result;        if (--expecting === 0) {            // Done!            console.log("Results:", results); // E.g., using the results        }    });});

Example:

var theArray = [1, 2, 3];var results = [];var expecting = theArray.length;theArray.forEach(function(entry, index) {    doSomethingAsync(entry, function(result) {        results[index] = result;        if (--expecting === 0) {            // Done!            console.log("Results:", JSON.stringify(results)); // E.g., using the results        }    });});function doSomethingAsync(value, callback) {    console.log("Starting async operation for "+ value);    setTimeout(function() {        console.log("Completing async operation for "+ value);        callback(value * 2);    }, Math.floor(Math.random() * 200));}
.as-console-wrapper { max-height: 100% !important; }

(We could do away with expecting and just use results.length === theArray.length, but that leaves us open to the possibility that theArray is changed while the calls are outstanding...)

Notice how we use the index from forEach to save the result in results in the same position as the entry it relates to, even if the results arrive out of order (since async calls don't necessarily complete in the order in which they were started).

But what if you need to return those results from a function? As the other answers have pointed out, you can't; you have to have your function accept and call a callback (or return a Promise). Here's a callback version:

function doSomethingWith(theArray, callback) {    var results = [];    var expecting = theArray.length;    theArray.forEach(function(entry, index) {        doSomethingAsync(entry, function(result) {            results[index] = result;            if (--expecting === 0) {                // Done!                callback(results);            }        });    });}doSomethingWith(theArray, function(results) {    console.log("Results:", results);});

Example:

function doSomethingWith(theArray, callback) {    var results = [];    var expecting = theArray.length;    theArray.forEach(function(entry, index) {        doSomethingAsync(entry, function(result) {            results[index] = result;            if (--expecting === 0) {                // Done!                callback(results);            }        });    });}doSomethingWith([1, 2, 3], function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value, callback) {    console.log("Starting async operation for "+ value);    setTimeout(function() {        console.log("Completing async operation for "+ value);        callback(value * 2);    }, Math.floor(Math.random() * 200));}
.as-console-wrapper { max-height: 100% !important; }

Or here's a version returning a Promise instead:

function doSomethingWith(theArray) {    return new Promise(function(resolve) {        var results = [];        var expecting = theArray.length;        theArray.forEach(function(entry, index) {            doSomethingAsync(entry, function(result) {                results[index] = result;                if (--expecting === 0) {                    // Done!                    resolve(results);                }            });        });    });}doSomethingWith(theArray).then(function(results) {    console.log("Results:", results);});

Of course, if doSomethingAsync passed us errors, we'd use reject to reject the promise when we got an error.)

Example:

function doSomethingWith(theArray) {    return new Promise(function(resolve) {        var results = [];        var expecting = theArray.length;        theArray.forEach(function(entry, index) {            doSomethingAsync(entry, function(result) {                results[index] = result;                if (--expecting === 0) {                    // Done!                    resolve(results);                }            });        });    });}doSomethingWith([1, 2, 3]).then(function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value, callback) {    console.log("Starting async operation for "+ value);    setTimeout(function() {        console.log("Completing async operation for "+ value);        callback(value * 2);    }, Math.floor(Math.random() * 200));}
.as-console-wrapper { max-height: 100% !important; }

(Or alternately, you could make a wrapper for doSomethingAsync that returns a promise, and then do the below...)

If doSomethingAsync gives you a Promise, you can use Promise.all:

function doSomethingWith(theArray) {    return Promise.all(theArray.map(function(entry) {        return doSomethingAsync(entry);    }));}doSomethingWith(theArray).then(function(results) {    console.log("Results:", results);});

If you know that doSomethingAsync will ignore a second and third argument, you can just pass it directly to map (map calls its callback with three arguments, but most people only use the first most of the time):

function doSomethingWith(theArray) {    return Promise.all(theArray.map(doSomethingAsync));}doSomethingWith(theArray).then(function(results) {    console.log("Results:", results);});

Example:

function doSomethingWith(theArray) {    return Promise.all(theArray.map(doSomethingAsync));}doSomethingWith([1, 2, 3]).then(function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value) {    console.log("Starting async operation for "+ value);    return new Promise(function(resolve) {        setTimeout(function() {            console.log("Completing async operation for "+ value);            resolve(value * 2);        }, Math.floor(Math.random() * 200));    });}
.as-console-wrapper { max-height: 100% !important; }

Note that Promise.all resolves its promise with an array of the results of all of the promises you give it when they are all resolved, or rejects its promise when the first of the promises you give it rejects.

Series

Suppose you don't want the operations to be in parallel? If you want to run them one after another, you need to wait for each operation to complete before you start the next. Here's an example of a function that does that and calls a callback with the result:

function doSomethingWith(theArray, callback) {    var results = [];    doOne(0);    function doOne(index) {        if (index < theArray.length) {            doSomethingAsync(theArray[index], function(result) {                results.push(result);                doOne(index + 1);            });        } else {            // Done!            callback(results);        }    }}doSomethingWith(theArray, function(results) {    console.log("Results:", results);});

(Since we're doing the work in series, we can just use results.push(result) since we know we won't get results out of order. In the above we could have used results[index] = result;, but in some of the following examples we don't have an index to use.)

Example:

function doSomethingWith(theArray, callback) {    var results = [];    doOne(0);    function doOne(index) {        if (index < theArray.length) {            doSomethingAsync(theArray[index], function(result) {                results.push(result);                doOne(index + 1);            });        } else {            // Done!            callback(results);        }    }}doSomethingWith([1, 2, 3], function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value, callback) {    console.log("Starting async operation for "+ value);    setTimeout(function() {        console.log("Completing async operation for "+ value);        callback(value * 2);    }, Math.floor(Math.random() * 200));}
.as-console-wrapper { max-height: 100% !important; }

(Or, again, build a wrapper for doSomethingAsync that gives you a promise and do the below...)

If doSomethingAsync gives you a Promise, if you can use ES2017+ syntax (perhaps with a transpiler like Babel), you can use an async function with for-of and await:

async function doSomethingWith(theArray) {    const results = [];    for (const entry of theArray) {        results.push(await doSomethingAsync(entry));    }    return results;}doSomethingWith(theArray).then(results => {    console.log("Results:", results);});

Example:

async function doSomethingWith(theArray) {    const results = [];    for (const entry of theArray) {        results.push(await doSomethingAsync(entry));    }    return results;}doSomethingWith([1, 2, 3]).then(function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value) {    console.log("Starting async operation for "+ value);    return new Promise(function(resolve) {        setTimeout(function() {            console.log("Completing async operation for "+ value);            resolve(value * 2);        }, Math.floor(Math.random() * 200));    });}
.as-console-wrapper { max-height: 100% !important; }

If you can't use ES2017+ syntax (yet), you can use a variation on the "Promise reduce" pattern (this is more complex than the usual Promise reduce because we're not passing the result from one into the next, but instead gathering up their results in an array):

function doSomethingWith(theArray) {    return theArray.reduce(function(p, entry) {        return p.then(function(results) {            return doSomethingAsync(entry).then(function(result) {                results.push(result);                return results;            });        });    }, Promise.resolve([]));}doSomethingWith(theArray).then(function(results) {    console.log("Results:", results);});

Example:

function doSomethingWith(theArray) {    return theArray.reduce(function(p, entry) {        return p.then(function(results) {            return doSomethingAsync(entry).then(function(result) {                results.push(result);                return results;            });        });    }, Promise.resolve([]));}doSomethingWith([1, 2, 3]).then(function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value) {    console.log("Starting async operation for "+ value);    return new Promise(function(resolve) {        setTimeout(function() {            console.log("Completing async operation for "+ value);            resolve(value * 2);        }, Math.floor(Math.random() * 200));    });}
.as-console-wrapper { max-height: 100% !important; }

...which is less cumbersome with ES2015+ arrow functions:

function doSomethingWith(theArray) {    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {        results.push(result);        return results;    })), Promise.resolve([]));}doSomethingWith(theArray).then(results => {    console.log("Results:", results);});

Example:

function doSomethingWith(theArray) {    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {        results.push(result);        return results;    })), Promise.resolve([]));}doSomethingWith([1, 2, 3]).then(function(results) {    console.log("Results:", JSON.stringify(results));});function doSomethingAsync(value) {    console.log("Starting async operation for "+ value);    return new Promise(function(resolve) {        setTimeout(function() {            console.log("Completing async operation for "+ value);            resolve(value * 2);        }, Math.floor(Math.random() * 200));    });}
.as-console-wrapper { max-height: 100% !important; }

Viewing all articles
Browse latest Browse all 46


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>