如何从含异步操作的循环返回数组?JavaScript异步问题求助
Hey there! Let's figure out why your code isn't behaving as expected and fix it, plus break down the JavaScript async concepts that are tripping you up here.
First, let's recap your code and what's happening:
Your Original Code
function foo(items) { var result = [] // Assume under forEach statement take 2 sec per loop items.forEach(function(item) { setTimeout(function() { result.push(item + 2) }, 2000) }) return result } console.log(foo([1,2,3,4])) console.log("Done")
Current Output
[] Done
Expected Output
[ 3, 4, 5, 6 ] Done
Why This Is Happening: JavaScript's Async & Event Loop
JavaScript is a single-threaded language, which means it can only do one thing at a time. But it handles async operations (like setTimeout) using something called the Event Loop. Here's the play-by-play of your code:
- When you call
foo([1,2,3,4]), the function starts running synchronously:- It creates an empty
resultarray. - It runs
forEachover the items. For each item, it schedules asetTimeoutcallback to run in 2 seconds—but this callback doesn't run right away. Instead, it gets added to the macrotask queue (a list of tasks waiting for the main thread to be free).
- It creates an empty
- Right after
forEachfinishes,fooreturns the emptyresultarray immediately—none of thesetTimeoutcallbacks have run yet! - The main thread then runs
console.log(foo(...)), which prints the empty array, followed byconsole.log("Done"). - Only after all these synchronous tasks are done does the Event Loop pick up the
setTimeoutcallbacks from the queue, one by one, and run them—pushing values intoresult... but by then, you've already printed the array and "Done".
How to Fix It
We need to wait for all the async setTimeout operations to finish before we access the result array. The cleanest way to do this in modern JavaScript is using Promises with async/await.
Solution 1: Async/Await + Promise.all
async function foo(items) { // Wrap each setTimeout in a Promise const asyncTasks = items.map(item => { return new Promise((resolve) => { setTimeout(() => { resolve(item + 2); // Resolve with the computed value }, 2000); }); }); // Wait for all Promises to resolve, then get the results array const result = await Promise.all(asyncTasks); return result; } // Since foo returns a Promise, we use .then() to handle the result foo([1,2,3,4]) .then(result => { console.log(result); // Logs [3,4,5,6] console.log("Done"); // Logs "Done" right after });
Let's break this down:
items.map(...)creates an array of Promises, one for each item. Each Promise resolves toitem + 2after 2 seconds.Promise.all(asyncTasks)waits for every Promise in the array to finish, then returns a single Promise that resolves to an array of all the results (in the same order as the original items).awaitpauses the execution of theasyncfunction until the Promise resolves, soresultwill be filled with the correct values when we return it.- We use
.then()to access the resolved result becauseasyncfunctions always return a Promise.
Alternative: Using Callbacks (Older Approach)
If you need to support older environments without async/await, you can use a callback to signal when all tasks are done:
function foo(items, callback) { var result = []; var completedTasks = 0; items.forEach(function(item) { setTimeout(function() { result.push(item + 2); completedTasks++; // Check if all tasks are done if (completedTasks === items.length) { callback(result); } }, 2000); }); } // Call foo with a callback to handle the result foo([1,2,3,4], function(result) { console.log(result); // Logs [3,4,5,6] console.log("Done"); // Logs "Done" });
Key Takeaways
- Synchronous code runs immediately: Any code without async operations (like variable declarations,
forEach,return) runs right away in the main thread. - Async tasks are queued: Operations like
setTimeout, AJAX calls, or Promise callbacks don't block the main thread—they're added to a queue and run only after all synchronous code is finished. - Use Promises/async/await to handle async results: If you need to wait for async tasks to finish before using their output, you can't just return a variable immediately—you need to use async patterns to wait for the results.
内容的提问来源于stack exchange,提问作者6LYTH3

