Calling an API from inside a loop with JavaScript
I needed to get data from an external api inside a loop while synchonizing the responses with the parameters that I sent. The project was written in jQuery so I tried working with jQuery built in promise utility. Here, I want to show you what doesn't work and what ends up working. At the end, I'm going to show how to solve the same problem with modern coding and the use of async-await.
I consider this writing a refresher on the subject of performing asynchronous tasks with JavaScript.
For the sake of the example I'm using the follwoing array to demonstrate the tasks order.
var tasks = [1,2,3,4,5]
The api returns back the exact parameter that we send to it.
My first thought was that something as simple as running a post request inside a loop can solve the problem:
var tasks = [1,2,3,4,5]
function perform1(x){
$.post('https://reshetech.co.il/dummy-api/', JSON.stringify({min: x, max: x}), function(res){
return res.random;
})
}
for(var x=0; x < tasks.length; x++){
var task = tasks[x];
console.log(task);
var res = perform1(task)
console.log({'task': task, 'result': res})
}
The result:
1 {task: 1, result: undefined} 2 {task: 2, result: undefined} 3 {task: 3, result: undefined} 4 {task: 4, result: undefined} 5 {task: 5, result: undefined}
The loop ran, sent the request and didn't wait for the response. Far from the result taht I needed.
The problem was that the code was asynchronous while the tools that I used were synchronous.
Since I needed to wait for the response to come back I implemented a promise inside the function:
var tasks = [1,2,3,4,5]
function perform2(x){
var d = $.Deferred();
$.post('https://reshetech.co.il/dummy-api/', JSON.stringify({min: x, max: x}), function(res){
d.resolve(res.random);
})
return d;
}
for(var x=0; x < tasks.length; x++){
var task = tasks[x];
console.log(tasks[x]);
perform2(tasks[x]).done(function(res){
console.log({'task': task, 'result': res})
})
}
The result:
1 2 3 4 5 {task: undefined, result: 1} {task: undefined, result: 3} {task: undefined, result: 4} {task: undefined, result: 2} {task: undefined, result: 5}
It looked like a step in the right direction since the code was able to wait for the response from the promise to resolve but the problem was that the returned result wasn't synchronized with the loop that finished running prior to getting the response.
To solve the problem I used the closure inside a forEach loop since it keeps the state:
var tasks = [1,2,3,4,5]
function perform2(x){
var d = $.Deferred();
$.post('https://reshetech.co.il/dummy-api/', JSON.stringify({min: x, max: x}), function(res){
d.resolve(res.random);
})
return d;
}
tasks.forEach(function(task){
// console.log(task);
perform2(task).done(function(res){
console.log({'task': task, 'result': res})
})
});
{task: 4, result: 4} {task: 1, result: 1} {task: 3, result: 3} {task: 2, result: 2} {task: 5, result: 5}
Exactly what I wanted. The code was able to synchronize the parameters that I sent to the api with the result once it came back.
A modern way to perform the same task is by using async-await and the fetch function:
const tasks = [1,2,3,4,5]
const api = 'https://reshetech.co.il/dummy-api/';
const perform3 = (x) => {
return fetch(api, {
method : 'post',
body: JSON.stringify({min: x, max: x})
})
.then((res) => res.json())
.catch((error) => console.log(error))
}
const managePerformance = async () => {
try {
for(let task of tasks){
// console.log(task)
const res = await perform3(task);
console.log({'task': task, 'result': res.random})
}
} catch(err) {
console.log(err);
}
}
managePerformance();
{task: 1, result: 1} {task: 2, result: 2} {task: 3, result: 3} {task: 4, result: 4} {task: 5, result: 5}
The result is the same but it doesn't rely on a framework and also the use of try-catch blocks makes the process safer.