Node FTP Multiple asynchronous calls inside loop
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When your Node.js application needs to upload or download multiple files from an FTP server, you typically iterate over a list of file paths and perform an FTP operation for each one. This is where asynchronous programming patterns become critical. Naively firing off all FTP calls at once can overwhelm the server, exhaust connections, or produce race conditions. This article walks through three approaches for handling multiple asynchronous FTP calls inside a loop: callbacks, promises with async/await, and controlled concurrency with p-limit.
The Problem with Naive Loops
Consider a list of files you need to upload to an FTP server. A common first instinct is to loop through them and call the upload function for each one.
This code fires all three uploads simultaneously on the same connection. Many FTP servers do not support concurrent transfers on a single connection, so this can fail with protocol errors or corrupted transfers. You need to either serialize the operations or manage concurrency properly.
Approach 1: Sequential Callbacks
The callback-based ftp library requires you to chain operations manually to ensure one completes before the next starts.
This recursive approach processes one file at a time. Each upload starts only after the previous one finishes. It works, but the nested callback structure becomes harder to read as complexity grows.
Approach 2: Promises with async/await
The promise-ftp library wraps FTP operations in promises, letting you use async/await for cleaner sequential code.
The for...of loop with await processes files one at a time, just like the callback approach, but the code reads like synchronous logic. Error handling uses a standard try/catch or .catch() on the returned promise.
Adding Error Handling
Wrap the loop body in a try/catch to handle individual file failures without aborting the entire batch.
Approach 3: Controlled Concurrency
Sequential processing is safe but slow. If you can open multiple FTP connections, you can upload files in parallel with a concurrency limit. The p-limit package provides a simple way to cap the number of concurrent operations.
Each call to uploadFile opens its own FTP connection, uploads the file, and closes the connection. The pLimit(3) wrapper ensures that at most 3 uploads run concurrently. This balances speed against server load.
Reusing a Connection Pool
For high-volume scenarios, opening a new connection per file is expensive. You can create a pool of FTP connections and distribute work across them.
Common Pitfalls
- Using
forEachwith async callbacks.Array.forEachdoes not await async callbacks. All iterations fire immediately, defeating the purpose of sequential processing. Use afor...ofloop withawaitinstead. - Not closing connections. Forgetting to call
ftp.end()leaves connections open. Over time, this exhausts the FTP server's connection limit and can cause your application to hang. - Ignoring server concurrency limits. Most FTP servers limit the number of simultaneous connections per user (often 3 to 10). Exceeding this limit results in connection refused errors. Always match your concurrency cap to the server's limits.
- Swallowing errors in Promise.all. If one promise in
Promise.allrejects, the entire batch rejects. UsePromise.allSettledif you want to process all files and collect errors afterward rather than failing fast. - Not handling stream errors.
fs.createReadStreamcan emit errors (file not found, permission denied). Attach an error handler to the stream or wrap the operation in try/catch to avoid unhandled exceptions.
Summary
When making multiple FTP calls inside a loop in Node.js, choose between sequential processing (safe for single connections), async/await with for...of (clean and readable), or controlled concurrency with p-limit (fast with parallel connections). Avoid forEach with async functions, always close connections, and respect server concurrency limits. For large batches, consider a connection pool to reduce the overhead of repeated connect/disconnect cycles.

