Multiple promise requests chained together in Javascript - javascript

I am attempting to chain together multiple asynchronous requests in JS. Basically, I want to retrieve the artist info from the LastFM API, then using that info lookup their top tracks.
So far, I can successfully get the artists info, have it return, and it prints out the info in the next step. However, once I try to do my second request for the top tracks, the body is never printed and it immediately moves on to the next step.
I have tried many many different combinations of this code with different kinds of requests and such but I haven't gotten any luck. I just want to successfully do request 1 if successful then follow it up by others in a proper order.
var artistInfo = {
method: 'GET',
url: 'http://localhost:3000/users/db/artistInfo/' + artistName
};
var topTracks = { method: 'GET',
url: 'http://localhost:3000/users/db/topTracks/' + artistName
};
/* Dependencies */
var Promise = require('bluebird');
var reqP = Promise.promisifyAll(require('request-promise'));
reqP(artistInfo)
.then(function(info) {
console.log("got here 1");
return info;
})
.then(function(artist) {
console.log(artist);
reqP(topTracks)
.then(function(body) {
console.log(body);
console.log("got here 2");
return body;
});
return 'test';
})
.then(function(content) {
console.log(content);
return 'test2';
})
.catch(function(err) {
throw err;
});

To sequence these two requests and have the outer .then() wait for both, you need to return the internal promise (the one inside the .then() handler) in order to chain them. If you don't return them, then then nothing is chained to the parent promise and therefore the parent promise doesn't wait for the child promise. See the line where I added return to return reqP(topTracks):
var artistInfo = {
method: 'GET',
url: 'http://localhost:3000/users/db/artistInfo/' + artistName
};
var topTracks = { method: 'GET',
url: 'http://localhost:3000/users/db/topTracks/' + artistName
};
/* Dependencies */
var Promise = require('bluebird');
var reqP = Promise.promisifyAll(require('request-promise'));
reqP(artistInfo)
.then(function(info) {
console.log("got here 1");
return info;
})
.then(function(artist) {
console.log(artist);
// *** add return here ***
return reqP(topTracks)
.then(function(body) {
console.log(body);
console.log("got here 2");
return body;
});
})
.then(function(content) {
console.log(content);
return 'test2';
})
.catch(function(err) {
throw err;
});
FYI, it doesn't look like your two requests depend upon one another so you could also do them in parallel:
var artistInfo = {
method: 'GET',
url: 'http://localhost:3000/users/db/artistInfo/' + artistName
};
var topTracks = { method: 'GET',
url: 'http://localhost:3000/users/db/topTracks/' + artistName
};
/* Dependencies */
var Promise = require('bluebird');
var reqP = Promise.promisifyAll(require('request-promise'));
Promise.all([reqP(artistInfo), reqP(topTracks)]).then(function(results){
// results[0] is artist
// results[1] is tracks
}, function(err){
// error here
});

First, I don't know why you are promisifying a promise-based library. There are a couple of ways to do this, but I am going to give you what I think is the cleanest way, but it requires bluebird (which you are using in your example anyway, so shouldn't be a problem)
It doesn't look like your second request depends on your first, you can use Promise.props()
/* Dependencies */
var Promise = require('bluebird');
var request = require('request-promise');
var artistInfo = request({
method: 'GET',
url: 'http://localhost:3000/users/db/artistInfo/' + artistName
});
var topTracks = request({
method: 'GET',
url: 'http://localhost:3000/users/db/topTracks/' + artistName
});
Promise.props({
artistInfo: artistInfo,
topTracks: topTracks
}).then(function(result) {
console.log(JSON.stringify(result.artistInfo,null,2));
console.log(JSON.stringify(result.topTracks,null,2));
})
.catch(function(err) {
console.error(err);
});
So what is going on here? Promise.props allows you to pass an object whose properties are promises. These promises will then execute in parallel and the promise won't be resolved until both are resolved (or it will drop to catch if either fail).
As for chaining promises, you need to make sure to return a promise. This is what allows you to avoid the ugly nesting
/* Dependencies */
var Promise = require('bluebird');
var request = require('request-promise');
request({
method: 'GET',
url: 'http://localhost:3000/users/db/artistInfo/' + artistName
})
.then(function(artistInfo) {
console.log(artistInfo);
return request({
method: 'GET',
url: 'http://localhost:3000/users/db/topTracks/' + artistName
});
})
.then(function(topTracks) {
console.log(topTracks);
})
.catch(function(err) {
console.error(err);
});

Related

Chaining the $http.get AngularJS

As I am making few call to the endpoints within a function and it was causing issue with the parallel calls to the endpoints, so it was suggested in the other question to use the promise chaining. I updated my code so we can call the endpoints one after the other, so the code looks like below
$scope.getRequest = function () {
var url = $rootScope.BaseURL;
var config = {
headers: {
'Authorization': `Basic ${$scope.key}`,
'Prefer': 'odata.maxpagesize=2000'
}
};
$http.get(url, config)
.then(newViewRequest)
.then(function(response){
$scope.viewRequest.data = response.data;
},
function (response) { // failure async
console.log("There was an error getting the request from CORE");});
};
var newViewRequest = function (response) {
var url1 = $rootScope.BaseURL + `CMQ_REQUEST('${$scope.viewRequest.barcode}')`;
if (response.data.REV_SAMPLE_CMQREQUEST.length = 0) {
return $http.get(url1, config)
}
return $q.reject({ message: 'Validations didnt work' });
};
It always sends the reject message back from the newViewRequest if response.data.REV_SAMPLE_CMQREQUEST.length = 0, if I comment it out I get the response.data is undefined.
Update your condition to validate instead of assigning
Issue: Update if condition as below to check response.data.REV_SAMPLE_CMQREQUEST.length whether it is 0 or not with === instead of =
if (response.data.REV_SAMPLE_CMQREQUEST.length === 0)

NodeJS - loop with nested API calls

Hello I'm new to NodeJs and am trying to work out the best way to get this chain of events working. I have to do two API calls get all the information I need. The first API call is just a list of IDs, then the second API call I pass the ID to get the rest of the information for each object.
However using the method below, I have no idea when everything is finished. Please can someone help me out.
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
obj.data.forEach(function(entry) {
findMore(entry.id)
});
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 2");
var obj = JSON.parse(apires);
})
}
You can make your findMore method return a promise, so you can pass an array of those to Promise.all and handle the .then when all promises have finished.
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
var promises = [];
obj.data.forEach(function(entry) {
promises.push(findMore(entry.id));
});
return Promise.all(promises);
})
.then(function (response) {
// Here response is an array with all the responses
// from your calls to findMore
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options);
}
A couple of things to think about:
If you care about the fate of a promise, always return it.
In your case, findMore does not return the promise from request, so getData has no handle to track the resolution (or rejection) of that promise.
You can track the resolution of multiple promises with Promise.all.
The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.
Lets put these to use on your example:
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
return request(options)
.then(function(apires){
var obj = JSON.parse(apires);
var findMorePromises = obj.data.map(function(entry) {
return findMore(entry.id)
});
return Promise.all(findMorePromises);
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options)
.then(function(apires){
return JSON.parse(apires);
})
}
I've used map to construct the array of promises, but you could just as well use a foreach and push into an array similar to be more similar to your example code.
It's also good practice to make sure you are handling rejection of any promises (via catch), but I'll assume that is out of the scope of this question.
You want to use Promise.all.
So first thing first, you need an array of promises. Inside your for each loop, set findMore to a variable, and make it return the promise. Then have a line where you do Promise.all(promiseArr).then(function(){console.log("done)})
Your code would look like this
function getData() {
var promiseArr = []
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
obj.data.forEach(function(entry) {
var p = findMore(entry.id)
promiseArr.push(p)
});
}).then(function(){
Promise.all(promiseArr).then(function(){
console.log("this is all done")
})
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options).then(function(apires){
console.log("complete 2");
var obj = JSON.parse(apires);
})
}
the basic idea of Promise.all is that it only executes once all promises in the array have been resolved, or when any of the promises fail. You can read more about it here
You need to use Promise.all to run all async requests in parallel. Also you must return the result of findMore and getData (they are promises).
function getData() {
var options = {...};
return request(options)
.then(function(apires) {
console.log("complete 1");
var obj = JSON.parse(apires);
var ops = obj.data.map(function(entry) {
return findMore(entry.id);
});
return Promise.all(ops);
}
function findMore(id) {
var options = {...};
return request(options)
.then(function(apires) {
console.log("complete 2");
return JSON.parse(apires);
});
}
getData()
.then(data => console.log(data))
.catch(err => console.log(err));
If you can use ES7, it can be written with async/await:
let getData = async () => {
let options = {...};
let res = awit request(options);
let ops = res.data.map(entry => findMore(entry.id));
let data = await Promise.all(ops);
return data;
};
let findMore = async (id) => {
let options = {...};
let apires = awit request(options);
return JSON.parse(apires);
};
EDIT: As others have mentioned, using a Promise.all() is likely a better solution in this case.
If you are open to using jQuery (a JavaScript library), then you can use the .ajaxStop() event handler and specify your own function. Sample code:
$(document).ajaxStop(function(){
alert("All AJAX requests are completed.");
});
You will need to include the jQuery module. The instructions for Node.js are:
Install module through npm:
npm install jquery
Then use a "require" to use jQuery in your JavaScript code (a window with a document is required but there is no such "window" in Node so you can mock one with jsdom), see npm - jQuery for details:
require("jsdom").env("", function(err, window) {
if (err) {
console.error(err);
return;
}
var $ = require("jquery")(window);
});
If you want to stick to a pure JavaScript approach, you will need to create your own "module" to keep track of AJAX requests. In this module you can keep track of how many pending requests there are and remove them once they are terminated. Please see: Check when all Ajax Requests are complete - Pure JavaScript for more details.

How can I run this function synchronously in node.js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I want to run the function synchronously. In my application
the supply source needs to be created before it is assigned to the other data.
And the application should only further if this task was fulfilled.
Because otherwise it will fail because the other data is created and no SupplySourceId is found (undefinded).
here i want to start the synchronous function (processSupplySource();)
var articleSupplySourceId = processSupplySource();
Function ProcessSupplySource:
function processSupplySource(){
var postJson2 = {};
postJson2.articleNumber = entry['part-no'];
postJson2.name = entry['part-no'];
postJson2.taxName = 'Vorsteuer';
postJson2.unitName = 'stk';
postJson2.supplierNumber = "1002";
postJson2.articlePrices = [];
var articlePrices = {};
articlePrices.currencyName = 'GBP';
articlePrices.price = entry['ek-preisgbp'];
articlePrices.priceScaleType = 'SCALE_FROM';
articlePrices.priceScaleValue = '1';
postJson2.articlePrices.push(articlePrices);
return postSupplySource(postJson2);
Function PostSupplySource
function postSupplySource(postJson2) {
rp({
method: 'POST',
url: url + '/webapp/api/v1/articleSupplySource',
auth: {
user: '*',
password: pwd
},
body: postJson2,
json: true
}).then(function (parsedBody) {
console.log('FinishArticleSupplySource');
var r1 = JSON.parse(parsedBody);
console.log(r1.id);
return r1.id;
})
.catch(function (err) {
console.log('errArticleSupplySource');
console.log(err.error);
// POST failed...
});
}
You can use async/await if you are using node 8 to get that synchronous behaviour you looking for.
Otherwise, you will need to use a library like deasync to wait for the post to complete and return the id.
You can wrap your postSupplySource function in a promise and call the other function when it resolves. This will ensure you have `sourceSupplyId' by the time you run the other functions. Except in case of an error. Something like this:
function postSupplySource(postJson2) {
return new Promise(resolve, reject){ //**added
rp({
method: 'POST',
url: url + '/webapp/api/v1/articleSupplySource',
auth: {
user: '*',
password: pwd
},
body: postJson2,
json: true
}).then(function (parsedBody) {
console.log('FinishArticleSupplySource');
var r1 = JSON.parse(parsedBody);
console.log(r1.id);
resolve(r1.id); // ** added
})
.catch(function (err) {
console.log('errArticleSupplySource');
console.log(err.error);
return reject(err); //*** added
// POST failed...
});
});
}
And then you can call the other function inside it like this:
postSupplySource(postJson2)
.then((supplySourceId) => {
// supplySourceId is available.
// you can call other functions here.
}).catch((err) => {
console.log(err);
});
Hope I got your question right.
As someone mentioned also, you can use asyc await.
As

Loop the object returned from node promise and feed to the next .then

I've been stuck with this problem for what seems to be an eternity.
I'm just getting into node, and starting to get my head around promises etc.
What I am trying to do is fetch data from the Spotify API, and the first thing I do is getting my own playlists:
function getPlaylists(access_token) {
var options = {
url: 'https://api.spotify.com/v1/me/playlists',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
return new Promise(function(resolve, reject) {
request.get(options, function(error, response, body) {
var playlists = body.items;
var playlistArray = [];
playlists.forEach(function(playlist) {
var name = playlist.name;
var url = playlist.tracks.href;
playlistArray.push(url);
});
if(!error) {
resolve(playlistArray);
} else {
reject(error);
}
});
});
}
All right, so far so good. Now I also want to fetch the artists from these playlists:
function getArtists(url,access_token) {
var params = {
url: url,
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
return new Promise(function(resolve, reject) {
request.get(params, function(error, response, body) {
var tracks = body.items;
var artistArray = [];
tracks.forEach(function(artists) {
let allArtists = artists.track.artists;
allArtists.forEach(function(artist) {
artistArray.push(artist);
});
})
if(!error) {
resolve(artistArray);
} else {
reject(error);
}
});
})
}
The way I return all of this data is:
getPlaylists(access_token)
.then(function(playlists) {
playlists.forEach(function(playlist) {
getArtists(playlist,access_token)
.then(function(artist) {
return artist;
});
});
}).then(function(artists) {
console.log("getting artists",artists);
}).catch(function(error) {
console.log(error);
})
However, this returns undefined. I can only make it work if I pass in a single playlist url to the getArtists function - the problem is the forEach loop which I don't know how to handle.
Any help is greatly appreciated!
You can use a combination of [].map() and Promise.all() to make a Promise that waits for the completion of an array of actions:
getPlaylists(access_token)
.then(playlists => Promise.all(playlists.map(playlist =>
getArtists(playlist, access_token)))
.then(artists => {
// use here
});
Remember, for Promises to chain you must return a value or a Promise from your .then() handler. .forEach() always returns undefined, no matter what.
First, we turn the array of playlist into an array of Promises by using .map() with getArtist, then we pass that array of Promises to Promise.all(), which returns a single Promise which resolves when all Promises in the array resolve (or rejects when the first rejects).

Parse.com and closures

I have a Cloud Code function that will execute n times the same block. The block consist in an http call with auth header. to make things simple, I have created a function at the root of my main.js. The function needs to return a result and keep in memory the authData (in order to reuse it for future calls).
function requestURI (uri){
var authData; // gets generated if null, should be reused if not null
var result; // supposingly contains the results
return something();
}
The function something() is a Parse.Promise because I need my calls to be asynchronous. As I understand I can't attach the result nor the authData to my promise.... If I run a console.log() within the function requestURI(), I see authData and result correctly populated as expected
Then I want this function from a Parse function. (the whole purpose is to have the function being re-usable by any other)
Parse.Cloud.define("testCall", function(request, response) {
var uri1 = '...';
var uri2 = '...';
var uri3 = '...';
return requestURI(uri1).then(function(){
// how do I get the result of my request?
return request(uri2);
}).then(function(){
// how do I get the result of my request?
return request(uri3);
});
}
The problem I have is that I can't retrieve my result out of the requestURI function and it seems that authData is reset everytime I run the function
I read the solution lies in closures but I can't get my head around them...
edit: add the function something():
return Parse.Cloud.httpRequest({
method: 'GET',
url: url,
headers: {
"Authorization" : digestAuthHeader
},
success: function(httpResponse) {
// all went well, let's increase the nonceCount, for future calls
authData["nc"] += 1;
// I need to return the result object in a promise
result = httpResponse.data;
// return a Promise that can be handled by any function
return Parse.Promise.as(result)); // this promise doesn't work
},
error: function(httpResponse) {
console.error('Request failed with response code ' + httpResponse.status);
return (null,Parse.Promise.error(httpResponse.text));
}
});
edit: here is what I'm trying
// authData is not null, we can make an authenticated call
function makeAuthenticatedRequest(){
// generate the appropriate auth Header;
var digestAuthHeader = generateDigestAuthHeader();
return Parse.Cloud.httpRequest({
method: 'GET',
url: url,
headers: {
"Authorization" : digestAuthHeader
}}).then(function(httpResponse) {
// all went well, let's increase the nonceCount, for future calls
authData["nc"] += 1;
// create the final object to return in a promise
result = httpResponse.data;
console.log(result) // returns something not null!!!
// return a Promise that can be handled by any function
return promise.resolve({'authData': authData, 'result': result});
},
function(error) {
console.error('Request failed with response code ' + error.status);
return (null,Parse.Promise.error(error));
});
}
Parse.Cloud.define("testCall", function(request, response) {
var uri1 = '...';
var authData;
return apiCall1001Menus(authData,uri1).then(function(result){
response.success(result); // returns {}
});
});
my response callback is {}!!! which is not what I would expect at all
I will give you an example to prevent my bad english cause misleading.
The following rewrite function makeAuthenticatedRequest() in callback deferred antipattern and promise fashion.
Callback:
Deferred antipattern:
function makeAuthenticatedRequest(){
// generate the appropriate auth Header;
var digestAuthHeader = generateDigestAuthHeader();
var promise = new Parse.promise();
Parse.Cloud.httpRequest({
method: 'GET',
url: url,
headers: {
"Authorization" : digestAuthHeader
},
success: function(httpResponse) {
// all went well, let's increase the nonceCount, for future calls
authData["nc"] += 1;
// create the final object to return in a promise
result = httpResponse.data;
console.log(result) // returns something not null!!!
// return a Promise that can be handled by any function
promise.resolve({'authData': authData, 'result': result});
},
error: function(error) {
console.error('Request failed with response code ' + error.status);
// it could be promise.resolve (success) or promise.reject (error)
promise.reject(error);
}
});
return promise;
}
Promise:
function makeAuthenticatedRequest(){
// generate the appropriate auth Header;
var digestAuthHeader = generateDigestAuthHeader();
return Parse.Cloud.httpRequest({
method: 'GET',
url: url,
headers: {
"Authorization" : digestAuthHeader
}
}).then(function(httpResponse) {
// all went well, let's increase the nonceCount, for future calls
authData["nc"] += 1;
// create the final object to return in a promise
result = httpResponse.data;
console.log(result) // returns something not null!!!
// return a Promise that can be handled by any function
return {'authData': authData, 'result': result};
}, function(error) {
console.error('Request failed with response code ' + error.status);
return Parse.Promise.error(error);
});
}

Categories

Resources