NodeJS: Good way to write Multiple API Calls in serial - javascript

Is there a better/more beautiful way to call multiple APIs after another (in serial) as in my example?
var request = require('request');
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
request('http://www.test.com/api1', function (error, response, body) {
if (!error && response.statusCode == 200) {
//And so on...
}
})
}
})
}
})

Depending on which version of node you are using, promises should be native...
https://nodejs.org/en/blog/release/v4.0.0/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
var request = require('request');
getRequest('http://www.test.com/api1').then(function (body1) {
// do something with body1
return getRequest('http://www.test.com/api2');
}).then(function (body2) {
// do something with body2
return getRequest('http://www.test.com/api3');
}).then(function (body3) {
// do something with body3
//And so on...
});
function getRequest(url) {
return new Promise(function (success, failure) {
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
success(body);
} else {
failure(error);
}
});
});
}

Use async.series
If you want to do same operation on a set of URLs, use async.map
Promise can also be used as others suggested.
If you are new with asynchronous programming, I suggest to start with async module then move to Promise (or coroutines, async/await) once you have clearer understanding.
Example:
var request = require('request');
async.series([
function(callback) {
request('http://www.test.com/api1', function(error, response, body) {
if (!error && response.statusCode == 200) {
return callback(null, response);
}
return callback(error || new Error('Response non-200'));
}
},
function(callback) {
request('http://www.test.com/api2', function(error, response, body) {
if (!error && response.statusCode == 200) {
return callback(null, response);
}
return callback(error || new Error('Response non-200'));
}
}
],
// optional callback
function(err, results) {
if (err) {
// Handle or return error
}
// results is now array of responses
});

You can use request-promise instead of request and then you would be able to chain all the promises!
https://github.com/request/request-promise
var rp = require('request-promise');
rp(options)
.then(function (body) {
return rp(...)
}).then(()){
...
}
In my honest opinion and if the order is not important you should do all the requests in parallel!

Use Promises.
https://www.promisejs.org
https://www.promisejs.org/patterns/
It is much better than callbacks and it can execute one request after another in serial.

You may want to consider using JavaScript Promises. They sure provide a more beautiful way to handle callbacks.

Related

NodeJS Request return JSON from function

I've read a couple of posts about this here (callbacks) but I still don't really fully understand how to solve my problem. So I was hoping that somebody here could help me with mine and I would get it better.
Simple put I want the ID I get from the first request to be used for the second request.
I'm new to JavaScript and NodeJS in general.
function idRequest(name) {
var options = {
...
};
function callback(error, response, body) {
if (response.statusCode == 200 && !error) {
const info = JSON.parse(body);
//console.log(info.accountId);
return info.accountId;
}
}
request(options, callback);
}
function requestById(accountId) {
var options = {
...
};
function callback(error, response, body) {
if (response.statusCode == 200 && !error) {
const info = JSON.parse(body);
console.log(info);
}
}
request(options, callback);
}
var id = idRequest('..');
requestById(id);
Try by returning a promise from the first function and inside it resolve the callback, so the once it is resolved , you can use it's then to trigger the second function
function idRequest(name) {
var options = {
...
};
function callback(error, response, body) {
if (response.statusCode == 200 && !error) {
const info = JSON.parse(body);
//console.log(info.accountId);
return info.accountId;
}
}
return new Promise(function(resolve, reject) {
resolve(request(options, callback))
})
}
function requestById(accountId) {
var options = {
...
};
function callback(error, response, body) {
if (response.statusCode == 200 && !error) {
const info = JSON.parse(body);
console.log(info);
}
}
request(options, callback);
}
var id = idRequest('..').then(function(data) {
requestById(data);
});
since callback is a async call, so var id will be undefined, when you call the requestById(id);
so either you can use the promise method, answered by #brk or you can call your requestById(id) function directly from the first callback.

Node.js execution doesn't continue after function in if-statement

This snippet of code gets executed until the end of the getRefreshToken function is at its last line. The play_song function does not get triggered.
Am I misunderstanding the way JS works? For now I have included the call to play_song at the end of the getRefreshToken function, and of course that works as expected. However, I am very confused why it doesn't just execute function after function in the if-statement.
function checkTime () {
var now = new Date();
if (now.getHours() == time[0] && now.getMinutes() == time[1]) {
getRefreshToken();
play_song();
}
}
The contents of getRefreshToken:
function getRefreshToken() {
console.log('Starting token request')
// requesting access token from refresh token
var refresh_token = tokens.refresh;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
//play_song(); //only a temporary fix
})
}
Yes in JavaScript, functions are not necessarily synchronous as you might be familiar with in other programming languages. JavaScript is single threaded, however operations such as fetching data, querying API's, databases, I/O etc. all happen in parallel to the main execution of your code.
I assume getRefreshToken() is making some sort of network request and the fact you mentioned play_song works when put at the end of getRefreshToken() gives it away.
There are many ways to deal with this in JavaScript, here's a good resource.
Here's an example for your situation, I removed some unneeded code for the purpose of the example:
// Using callbacks
function checkTime() {
getRefreshToken(play_song);
}
function getRefreshToken(callback) {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
callback();
})
}
// Using Promises
function checkTime() {
getRefreshToken()
.then(() => {
play_song();
})
}
function getRefreshToken(callback) {
return new Promise((resolve, reject) => {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
resolve();
})
})
}
// Using Async/Await
async function checkTime() {
await getRefreshToken()
play_song();
}
function getRefreshToken(callback) {
return new Promise((resolve, reject) => {
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
tokens.access = body.access_token;
}
resolve();
})
})
}

Retry promise himself after fail in node js

I would like to retry my request in a promise. I would like launch my refresh if I have always an 401 error as a loop : (if I have 401 loop on refresh until 200)
I tried with this :
const request = require('request');
let conf = require('../conf');
let core_service = require('coreService');
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
core_service.refreshToken().then((data) => {
console.log('token refresh');
return self.get_count_questions();
})
} else {
reject(error);
}
})
});
}
};
I tried with just 'self.get_count_questions();' without return, but it's not work. I have not error message, just my app freeze.
I see in my console.log "token refresh", but after my app freeze...
Edit
I modified with this, It's like better but the refresh token it's very slow. Just before 401, my app stop, and after about 1 minutes 40 seconds, run:
else if (!error && response.statusCode === 401) {
console.log('need refresh token');
core_service.refreshToken()
.then((response) => {
console.log(response);
resolve(self.get_count_questions())
} );
}
My refreshToken function :
refreshToken: function () {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri : 'http://api/refresh',
auth : {
'bearer': conf.token
},
json : true
}, function (error, response, body) {
console.log('=====> refresh token <======');
conf.token = body.data;
console.log('new Token');
console.log('=====> end refresh token <======');
if (!error && response.statusCode === 200) {
resolve('Refresh token successful');
} else {
reject('Error refresh');
}
})
});
}
If I refresh my token on each request, I have a problem :
if (!error && response.statusCode === 200) {
core_service.refreshToken().then((data)=> {
resolve(body);
});
}
You have to resolve the returned promise. When you resolve using a promise, you basically say, complete this promise with the result of that promise.
var prom = function() {
return new Promise((resolve, reject) => {
console.log('request start')
setTimeout(() => {
console.log('request finish')
let ran = Math.random();
if (ran < 0.1)
resolve('success');
else if (ran >= 0.1 && ran < 0.98)
setTimeout(() => {
console.log('retry');
resolve(prom());
}, 500);
else
reject('error');
}, 500);
});
};
prom().then(console.log.bind(console), console.log.bind(console));
So you should update your else if block like this:
else if (!error && response.statusCode === 401) {
console.log('need refresh token');
core_service.refreshToken()
.then(() => resolve(self.get_count_questions()));
}
You're making a recursive call, but you're never actually doing anything with its promise. Therefore, your original promise never resolves.
You need to pass the promise from the recursive call (to refreshToken().then()) to resolve().
Now you almost have it.
However:
return core_service.refreshToken()
.then(self.get_count_questions);
You're returning that to the request() callback; that return value is not used.
Instead, you need to resolve your original promise to the new promise from then(), by passing it to your original resolve() function:
resolve(core_service.refreshToken().then(...));
I know that is not optimal solution but it might helps
const request = require('request');
let conf = require('../conf');
let core_service = require('coreService');
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
try{
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
throw new Error(response.statusCode);
} else {
reject(error);
}
}catch(exc){if(exc === 401){
core_service.refreshToken().then((data) => {
console.log('token refresh');
return self.get_count_questions();
})
}
}
})
});
}
};
You need to call the initial resolve/reject functions after you retried the request:
let self = module.exports = {
get_count_questions: function() {
return new Promise((resolve, reject) => {
request({
method: 'GET',
uri: 'http://api/count-questions',
auth: {
'bearer': conf.token
},
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
} else if (!error && response.statusCode === 401) {
core_service.refreshToken().then((data) => {
console.log('token refresh');
self.get_count_questions().then((data) => {
// call initial resolve function
resolve(data);
}).catch((error) => {
// call initial reject function
reject(error);
});
}).catch((error) => {
// reject if refreshToken fails
reject(error);
});
} else {
reject(error);
}
})
});
}
};
You also have to make sure, that the second call actually resolves/rejects and doesn't land in another 401. Because else you have an infinite recursion.

JavaScript Promise - how to make multiple promise?

How can I chain the multiple promise? For instance:
var promise = new Promise(function(resolve, reject) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Resolve the result.
resolve(true);
} else {
reject(Error(false));
}
});
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body); // <-- I want to pass the result to the next promise.
} else {
reject(Error(false));
}
});
}
}, function(err) {
// handle error
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
// handle success.
console.log(result);
}
}, function(err) {
// handle error.
});
Error:
resolve(body);
ReferenceError: resolve is not defined
Any ideas?
When chaining Promises, then return value from a function given to then should either be a Promise, or the value to pass along.
In your case, since you're making an async call, you'll just return another promise and call reject or resolve within there. If it wasn't async, you could just return the value, or throw an error, which also gets passed along to the next then or error handler/catch as appropriate.
Also, you need to chain them together, because each then() returns a different Promise.
So, something like this:
var promise = new Promise(function(resolve, reject) {
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Resolve the result.
resolve(true);
} else {
reject(Error(false));
}
});
});
promise.then(function(result) {
// Stop here if it is false.
if (result !== false) {
var airportCode = result;
// Compose the pull url.
var pullUrl = 'xxx';
// Use request library.
return new Promise(function (resolve, reject) {
request(pullUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
} else {
reject(Error(false));
}
});
});
}
}).then(function(result) {
// Stop here if it is false.
if (result !== false) {
// handle success.
console.log(result);
}
}).catch(function (err) {
// handle error
});
Here is a JSFiddle with a working version: JSFiddle

node.js: how to implement a routing method with an async call inside a forEach loop?

I have to implement a logic like this:
var express = require('express'),
request = require('request');
var array = [
{ url: 'http://www.example1.com' },
{ url: 'http://www.example2.com' },
];
express.Router().get('/job', function (req, res) {
results = [];
array.forEach(function (item) {
request(item.url, function (error, response, body) {
if (!error && response.statusCode == 200) {
results.push(body);
}
});
res.json(results); // <== wrong, results array is empty, here...
});
How do I call res.json(results) being sure forEach loop is ended?
UPDATE: it is not a duplicate of suggested answer!
I need to res.json() when all requests are completed, and not when forEach loop is finished... :-(
A simple way to accomplish this:
var express = require('express'),
request = require('request');
var array = [
{ url: 'http://www.example1.com' },
{ url: 'http://www.example2.com' },
];
//store number of completed calls
var completed = 0;
express.Router().get('/job', function (req, res) {
results = [];
array.forEach(function (item) {
request(item.url, function (error, response, body) {
if (!error && response.statusCode == 200) {
results.push(body);
completed = completed + 1; //iterate counter
onComplete(); //attempt completion
}
});
function onComplete(){
if(completed != array.length) {
return;
}
res.json(results);
// ... rest of code that relies on results
};
});
You can use asnyc series to achieve this. Something like this:
var express = require('express'),
request = require('request');
async = require('async);
...
async.series([
function(callback) {
request('http://www.example1.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
callback(null, body);
}
});
},
function(callback) {
request('http://www.example2.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
callback(null, body);
}
});
}
], function(err, res) {
res.json(res);
});
You should use promises! (This example works with last version of node because supports native promises)
var arrayPromises = [];
array.forEach(function (item) {
arrayPromises.push(new Promise(function(resolve, reject) {
request(item.url, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
}else{
resolve();
}
});
}));
});
Promise.all(arrayPromises).then(function(results){
res.json(results);
});

Categories

Resources