Javascript promises: how to deal with token expiry in API calls? - javascript

When I am receiving an error from my API that my token has expired I want to be able to re-issue a new token and repeat the request as first sent without the user feeling any difference. Is there a way to handle this use case elegantly?
function relogin(userInfo) {
return Q.Promise(function (resolve, reject) {
// ...
});
}
function makeTransaction(token, options) {
dataOperations.makeTransaction(token, options).then(function (result) {
// notify ui - new data yey!
}).catch(function (err) {
if (err.code === 304) {
relogin.then(function (token) {
makeTransaction(token, options);
}).catch(function (err) {
// notify UI - problems with server
});
}
});
}

that code looks pretty good already. what specifically is your issue? If you have many transactions and you're worried about code duplication, and checking for HTTP 304 many times, you could wrap your transactions in a handler:
function relogin(userInfo) {
return Q.Promise(function(resolve, reject) {
// ...
});
}
function myTask(params) {
return Q.Promise(function(resolve, reject) {
// ...
});
}
function doTask(fn, context, params) {
return fn.apply(context, params).then(function(results) {
return results;
}).catch(function(err) {
if (err.code === 304) {
return relogin({}).then(doTask(fn, context, params));
}
else {
return err;
}
});
}
// ...
$("#foo").on("click", function(e) {
doTask(myTask, this, params).done(function(results) {
alert("all done");
}).catch(function(err) {
alert(err);
});
});

Related

How to handle reject promise error in outer try catch with inner Promise.all?

When an error/rejection occurs in detectingDog or detectingDog, the error is successfully handled by the .catch(error of the Promise.all() but I want the error to be directly handled by the catch (err) of the try structure.
How can I do this ?
PS: I have already tried to get rid of the .catch(error but then the Promise.all() hangs forever
try {
function detectingDog(bufferedData) {
return new Promise((resolve, reject) => {
package.detectDog(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
return resolve(data);
}
});
});
}
function detectingCat(bufferedData) {
return new Promise((resolve, reject) => {
package.detectCat(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
return resolve(data);
}
});
});
}
Promise.all([
detectingDog(param1),
detectingCat(param2)
]).then(responseData => {
callback(undefined, responseData);
}).catch(error => {
// (1) I need to pass the error to the outer structure where error handling is done
});
} catch (err) {
console.log(err);
// handing of the inner error (2) here
callback(err);
}
Thanks!
...but I want the error to be directly handled by the catch (err) of the try structure.
You can't do that in a non-async function, because control has already left the try/catch by the time that rejection occurs, which is after whatever function this code is in (if any) has returned.
In an async function, you can use await on a promise, which will make a rejection throw, so it would go to your try/catch. So you could do the following, but keep reading because it's fairly odd:
// In an `async` function
try {
function detectingDog(bufferedData) {
return new Promise((resolve, reject) => {
package.detectDog(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
return resolve(data);
}
});
});
}
function detectingCat(bufferedData) {
return new Promise((resolve, reject) => {
package.detectCat(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
return resolve(data);
}
});
});
}
const responseData = await Promise.all([
detectingDog(param1),
detectingCat(param2)
]);
callback(responseData);
} catch (err) {
console.log(err);
callback(err);
}
...but it doesn't make a lot of sense to go to the trouble of converting callback APIs to promises if you're just going to provide a callback-based API to your caller. Just return a promise. That makes the whole try/catch disappear:
// No need for these to be nested
function detectingDog(bufferedData) {
return new Promise((resolve, reject) => {
package.detectDog(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
resolve(data); // No need for `return`
}
});
});
}
function detectingCat(bufferedData) {
return new Promise((resolve, reject) => {
package.detectCat(bufferedData, function(error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
function example(param1, param2) {
return Promise.all([
detectingDog(param1),
detectingCat(param2)
]);
}
You have two options here.
If you really need the try/catch block you will need to run your code in an async function, leveraging the fact that awaiting a rejected Promise will throw an error in this context:
(async function () { // you might not need the immediately invoking function wrapper depending on your context
try {
function one(bufferedData) {
// return a promise
}
function two(bufferedData) {
// return a Promise
}
const responseData = await Promise.all([
one(param1),
two(param2)
])
callback(undefined, responseData)
} catch (err) {
console.log(err);
// handing of the inner error (2) here
callback(err)
}
})()
Alternatively, you can also just handle the error in the catch block of your Promise chain:
function one(bufferedData) {
// return a promise
}
function two(bufferedData) {
// return a Promise
}
Promise.all([
one(param1),
two(param2)
])
.then((responseData) => {
callback(undefined, responseData)
})
.catch((err) => {
console.log(err);
// handing of the inner error (2) here
callback(err)
})

While call rest api in Javascript (NodeJS) and return response Undefined

When i call rest API and return response it show undefined but i console.log this response it return
var request = require("request");
function initialize() {
// Setting URL and headers for request
var options = {
url: 'http://postalpincode.in/api/pincode/400605',
json: true
};
// Return new promise
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.stringify(body));
}
})
})
}
function main() {
var initializePromise = initialize();
initializePromise.then(function (result) {
return result;
})
}
console.log('', main())
But when i console log this response it show output correct
var request = require("request");
function initialize() {
// Setting URL and headers for request
var options = {
url: 'http://postalpincode.in/api/pincode/400605',
json: true
};
// Return new promise
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.stringify(body));
}
})
})
}
function main() {
var initializePromise = initialize();
initializePromise.then(function (result) {
console.log('', result)
})
}
console.log('', main())
I want When i call rest API and return response it show correct output
The return inside the thenscope is not returning for the function main, but only for the thenscope of the promise. You need to return the promise like so:
function main() {
var initializePromise = initialize();
return initializePromise.then(function (result) {
return result;
})
}
main().then((result) => console.log('',result));
you can't make a sync function call an async method and expect to have get its result.
use async/await
async function main() {
var initializePromise = await initialize();
console.log(initializePromise)
}
My question is, why are you wrapping in a new Promise something that's already from a return type of Promise?
You could just do:
request.get(endpoint, options).then((response) => console.log(response)).catch((error) => console.log(error));
Let me know what's the output in that case.
The then resolution of initializePromise method resolves at a later stage when a response is fetched from REST HTTP call i.e. it does not get returned when you call main() method due to the fact it is async. To handle such code, you should either use a callback
function main(completionHandler) {
var initializePromise = initialize();
initializePromise.then(function (result) {
completionHandler(result);
})
}
main((result) => { console.log(result)})
or a promise
function main() {
// Return new promise
return new Promise(resolve => {
var initializePromise = initialize();
initializePromise.then(function (result) {
resolve(result);
})
}
}
main().then(result => console.log(result));
return new Promise(function (resolve, reject) {
// Do async job
request.get(options, function (err, resp, body) {
if (err) {
reject(err);
} else {
try {
resolve(JSON.stringify(body));
} catch(e) {
reject(e);
}
}
})
})
in main function:
function main() {
initialize().then((result) => {
console.log(result);
return result;
}).catch((err) => {
console.log(err);
return err;
})
}

unable to resolve inner promise in javascript

Unable to resolve or come out of inner promise so I can send the data back to client side.
Code:
function getAmazonData(url, isRatings) {
var parsedData;
var ItemLookup;
var ratingsURL;
return new Promise(function (resolve, reject) {
request(url, function (err, response, body) {
if (err) return reject(err);
parseXML(body, function (err, parsedData) {
if (err) return reject(err);
ItemLookup = parsedData['ItemLookupResponse'];
if (ItemLookup && ItemLookup.Items && ItemLookup.Items.length > 0) {
// Request Error
if (ItemLookup.Items[0].Request && ItemLookup.Items[0].Request.length > 0 && ItemLookup.Items[0].Request[0].Errors) {
return reject(ItemLookup.Items[0].Request[0].Errors);
} else if (ItemLookup.Items[0].Item) {
ratingsURL = ItemLookup.Items[0].Item[0].CustomerReviews[0].IFrameURL[0];
if (isRatings) {
console.log('getting ratings...');
return new Promise(function (resolve2, reject2) {
request(ratingsURL, { json: true }, function (ratingsError, ratingBody) {
if (ratingsError) {
return reject2('Error in getting ratings');
}
ItemLookup.Items[0].Item[0].CustomerReviews.push({ Ratings: ratingBody });
resolve2(ItemLookup.Items[0].Item[0].CustomerReviews);
});
});
}
console.log('Resolved: ', ItemLookup.Items[0].Item);
resolve(ItemLookup.Items[0].Item);
}
}
});
});
});
}
amazon_router.get('/getDetails', function (req, res, next) {
var amazonApi = decodeURIComponent(req.query.url);
var amzonApiUrl = amazonApi.replace(/\+/g, '%2B');
var isRatings = decodeURIComponent(req.query.ratings);
console.log('');
console.log('amazon api url ', amzonApiUrl);
console.log('');
getAmazonData(amzonApiUrl, isRatings).then(function (err, response) {
if (err) res.send(err);
res.send(response);
});
});
The data is never sent after resolving promise here at LINE 10: res.send(response); There's something wrong with inner promise but I am not sure not do I fix it?
It looks like you are mixing callbacks and Promises. I would recommend wrapping all calls to Promises.
From the looks of it you don't need inner Promise because request expects a callback so you can easily resolve the outer one.
You also have some branches of if statements that don't call resolve or reject (marked in the code bellow with comments). You have to call resolve/reject in all branches otherwise the Promise 'might do nothing' in case it hits the branch.
function getAmazonData(url, isRatings) {
var parsedData;
var ItemLookup;
var ratingsURL;
return new Promise(function (resolve, reject) {
request(url, function (err, response, body) {
if (err) return reject(err);
parseXML(body, function (err, parsedData) {
if (err) return reject(err);
ItemLookup = parsedData['ItemLookupResponse'];
if (ItemLookup && ItemLookup.Items && ItemLookup.Items.length > 0) {
// Request Error
if (ItemLookup.Items[0].Request && ItemLookup.Items[0].Request.length > 0 && ItemLookup.Items[0].Request[0].Errors) {
reject(ItemLookup.Items[0].Request[0].Errors);
} else if (ItemLookup.Items[0].Item) {
ratingsURL = ItemLookup.Items[0].Item[0].CustomerReviews[0].IFrameURL[0];
if (isRatings) {
console.log('getting ratings...');
request(ratingsURL, { json: true }, function (ratingsError, ratingBody) {
if (ratingsError) {
reject(new Error('Error in getting ratings'));
return;
}
ItemLookup.Items[0].Item[0].CustomerReviews.push({ Ratings: ratingBody });
resolve(ItemLookup.Items[0].Item[0].CustomerReviews);
});
} else {
console.log('Resolved: ', ItemLookup.Items[0].Item);
resolve(ItemLookup.Items[0].Item);
}
} else {
// resolve or reject here
}
} else {
// resolve or reject here
}
});
});
});
}
Wrapping call to Promise:
function requestAsPromised(url) {
return new Promise(function (resolve, reject) {
request(url, function (err, response, body) {
if (err) {
reject(err);
} else {
resolve(body)
}
});
})
}
You need to use return to return the inner promise(s),
function getAmazonData(url, isRatings) {
var parsedData;
var ItemLookup;
var ratingsURL;
return new Promise(function (resolve, reject) {
return request(url, function (err, response, body) {
if (err) return reject(err);
return parseXML(body, function (err, parsedData) {
if (err) return reject(err);
ItemLookup = parsedData['ItemLookupResponse'];
if (ItemLookup && ItemLookup.Items && ItemLookup.Items.length > 0) {
// Request Error
if (ItemLookup.Items[0].Request && ItemLookup.Items[0].Request.length > 0 && ItemLookup.Items[0].Request[0].Errors) {
return reject(ItemLookup.Items[0].Request[0].Errors);
} else if (ItemLookup.Items[0].Item) {
ratingsURL = ItemLookup.Items[0].Item[0].CustomerReviews[0].IFrameURL[0];
if (isRatings) {
console.log('getting ratings...');
return new Promise(function (resolve2, reject2) {
request(ratingsURL, { json: true }, function (ratingsError, ratingBody) {
if (ratingsError) {
return reject2('Error in getting ratings');
}
ItemLookup.Items[0].Item[0].CustomerReviews.push({ Ratings: ratingBody });
resolve2(ItemLookup.Items[0].Item[0].CustomerReviews);
});
});
}
console.log('Resolved: ', ItemLookup.Items[0].Item);
return resolve(ItemLookup.Items[0].Item);
}
}
});
});
});
}
Since it looks like Node.js code, try using async/await

How to use json response to create another URL. Nodejs

I have the question, below code:
The problem is:
How can I send each line from response promiseGetCitiesData to promiseGetInformationDataPerCity.
Can I do it in one async.each functions?
Now, I created multiple Promise functions. One general function, which one start the program - getDataAndCloseDb().
Also I used async.each to call promise function with array parameter - locationArray.
Now, I would like to send each line from json response to next promise function (create get url), and collect the general response.
const MongoClient = require("mongodb").MongoClient;
const request = require("request");
const async = require("async");
var locationsArray = [
'location1',
'location2',
'location3'
];
function promiseConnectToDatabase(urldb) {
return new Promise(function(resolve, reject) {
MongoClient.connect(urldb, (err, db) => {
if (err) {
console.log("MongoDb connection error.");
reject(err);
}
console.log("Connected to MongoDb.");
resolve(db);
});
});
}
function promiseGetCitiesData(location) {
return new Promise(function(resolve, reject) {
request({
url: `https://example.com/${location}`,
json: true
}, (error, response, body) => {
if (error) {
console.log("Error connection to url.");
reject();
}
console.log("location: " + location);
console.log({location: location, cities: body.result.cities});
resolve({location: location, cities: body.result.cities});
});
});
}
/*
Example response from promiseGetCitiesData:
Location: location1
{ location: 'location1',
cities:
[ 'information1',
'information2',
'information3',
'information4'' ] }
*/
function promiseGetInformationDataPerCity(location, cities) {
return new Promise(function(resolve, reject) {
request({
url: `https://example.com/${location}/${cities}`,
//f.e https://example.com/location1/information1 etc.
json: true
}, (error, response, information) => {
if (error) {
console.log("Error connection to url.");
reject();
}
console.log(information);
resolve(information);
});
});
}
function promiseSaveDataToDatabase(db, body) {
return new Promise(function(resolve, reject) {
db.collection("testlocation").insert(body, function(dbError) {
if (dbError) {
reject(dbError);
}
resolve()
});
});
}
function promiseDisconnectDatabase(db) {
return new Promise(function(resolve, reject) {
db.close((err) => {
if (err) {
console.log("MongoDb disconnect error.");
reject(err);
}
console.log("MongoDb disconnected.");
resolve();
});
});
}
function promiseProvideDataFromEach(locationsArray, db) {
return new Promise(function(resolve, reject) {
async.each(locationsArray, function(loc, locProcessedCb) {
promiseGetcitiesData(loc).then(function(resultscities) {
promiseGetInformationDataPerCity(loc, resultscities).then(function(resultDetails) {
promiseSaveDataToDatabase(db, resultDetails).then(function() {});
locProcessedCb();
});
});
}, function(err) {
if (err) {
locProcessedCb(err);
reject(err);
}
console.log("All locations have been processed.");
resolve();
});
});
}
function getDataAndCloseDb() {
return new Promise(function(resolve, reject) {
promiseConnectToDatabase("mongodb://127.0.0.1:27017/testApp").then(function(db) {
promiseProvideDataFromEach(locationsArray, db).then(function() {
promiseDisconnectDatabase(db).then(function() {});
});
});
});
}
getDataAndCloseDb();
I think this is a lot simpler than the code in the question makes it appear. In particular, new Promise(...) can be completely avoided by :
using require('async-request') instead of require('request').
allowing MongoDb methods to return Promise, as many of them will do if no callback is passed.
Also
by using the Promise.all(array.map(...)) pattern the need for require('async') disappears.
https://stackoverflow.com/a/28915678/3478010 - provides a great little reusable disposer utility, which is useful here.
Remember to return a promise/value from every .then() callback that is itself asynchronous and/or should deliver data.
With some guesswork, I think you want something like this :
const MongoClient = require('mongodb').MongoClient;
const request = require('async-request'); // just like `request()` but returns a promise
var locationsArray = [
'location1',
'location2',
'location3'
];
function promiseGetCitiesData(loc) {
return request({
url: `https://example.com/${loc}`,
json: true
}).then(body => body.result.cities);
}
function promiseGetInformationDataPerCity(loc, cities) {
return Promise.all(cities.map(city => {
return request({
'url': `https://example.com/${loc}/${city}`,
'json': true
}).then(cityInfo => ({ 'name':city, 'info':cityInfo }));
}));
}
function promiseProvideDataFromEach(locationsArray, db) {
return Promise.all(locationsArray.map(loc => {
return promiseGetCitiesData(loc)
.then(cities => promiseGetInformationDataPerCity(loc, cities)
.then(citiesWithCityInfo => ({ 'location':loc, 'cities':citiesWithCityInfo }));
}))
.then(resultDetails => db.collection('testlocation').insertMany(resultDetails));
}
// disposer utility - credit: https://stackoverflow.com/a/28915678/3478010
function withDb(work) {
var _db;
return MongoClient.connect("mongodb://127.0.0.1:27017/testApp")
.then((db) => {
_db = db; // keep reference
return work(db); // perform work on db
}).finally(() => {
if (_db)
_db.close();
});
}
withDb(db => promiseProvideDataFromEach(locationsArray, db))
.then(() => {
// connection released here
});
The guesswork centres mainly around what is to be inserted at db.collection('testlocation').insertMany(resultDetails). The code in the question gives no more than a clue. My attempt seems reasonable but may not be exactly what you want. Be prepared to make some changes in promiseProvideDataFromEach() and promiseGetInformationDataPerCity().
you can do something like this. Its a simpler code but I think you can map it to your current code.
const Promise = require('bluebird')
const cities = ['citya', 'cityb', 'cityc']
function resolveCities() {
return new Promise(function(resolve, reject) {
resolve(cities)
})
}
function logCity(city) {
console.log('city ', city)
}
return resolveCities()
.then(function(cities) {
return Promise.mapSeries(cities, function(city) {
logCity(city);
});
})

Recursively calling asynchronous function that returns a promise

I'm trying to recursively call AWS's SNS listEndpointsByPlatformApplication. This returns the first 100 endpoints then a token in NextToken if there are more to return (details: AWS SNS listEndpointsByPlatformApplication).
Here's what I've tried:
var getEndpoints = function(platformARN, token) {
return new models.sequelize.Promise(function(resolve, reject) {
var params = {
PlatformApplicationArn: platformARNDev
};
if (token != null) {
params['NextToken'] = token;
}
sns.listEndpointsByPlatformApplication(params, function(err, data) {
if (err) {
return reject(err);
}
else {
endpoints = endpoints.concat(data.Endpoints); //save to global var
if ('NextToken' in data) {
//call recursively
return getEndpoints(platformARN, data.NextToken);
}
else {
console.log('trying to break out!');
return resolve(true);
}
}
});
});
}
I'm calling it with:
getEndpoints(platformARNDev, null)
.then(function(ret) {
console.log('HERE!');
}, function(err) {
console.log(err);
});
Problem is: the first call happens, then the recursive call happens, and I get the message trying to break out! but the HERE! never gets called. I've got something wrong with how my promises are returning I think.
Grateful for pointers.
The problem is that you try and resolve/reject partially completed query. Here is a complete working example with dummy service. I incapsulated the data grabbing into it's own recursive function and only do resolve/reject when i've completely fetched all the data or stumbled upon an error:
// This is the mock of the service. It yields data and token if
// it has more data to show. Otherwise data and null as a token.
var dummyData = [0, 1, 2, 3, 4];
function dummyAsyncCall(token, callback) {
token = token || 0;
setTimeout(function() {
callback({
dummyDataPart: dummyData[token],
token: (typeof (dummyData[token]) == 'undefined') ? null : (token + 1)
});
});
}
// Here is how you would recursively call it with promises:
function getAllData() {
//data accumulator is sitting within the function so it doesn't pollute the global namespace.
var dataSoFar = [];
function recursiveCall(token, resolve, reject) {
dummyAsyncCall(token, function(data) {
if (data.error) {
reject(data.error);
}
if (!data.token) {
//You don't need to return the resolve/reject result.
resolve(dataSoFar);
} else {
dataSoFar = dataSoFar.concat(data.dummyDataPart);
recursiveCall(data.token, resolve, reject);
}
});
}
return new Promise(function(resolve, reject) {
// Note me passing resolve and reject into the recursive call.
// I like it this way but you can just store them within the closure for
// later use
recursiveCall(null, resolve, reject);
});
}
//Here is the call to the recursive service.
getAllData().then(function(data) {
console.log(data);
});
Fiddle with me
That's because you dont need to return resolve/reject, just call resolve/reject when the recursive call completes. A rough code would look like this
var getEndpoints = function(platformARN, token) {
return new models.sequelize.Promise(function(resolve, reject) {
var params = {
PlatformApplicationArn: platformARNDev
};
if (token != null) {
params['NextToken'] = token;
}
sns.listEndpointsByPlatformApplication(params, function(err, data) {
if (err) {
reject(err);
}
else {
endpoints = endpoints.concat(data.Endpoints); //save to global var
if ('NextToken' in data) {
//call recursively
getEndpoints(platformARN, data.NextToken).then(function () {
resolve(true);
}).catch(function (err) {
reject(err);
});
}
else {
console.log('trying to break out!');
resolve(true);
}
}
});
});
}
(caution: this is just a rough code, may work or may not, but is to give a general idea)
I've added a code snippet below, to support this concept, and it works great, check it out.
i = 0;
$('#output').empty();
function pro() {
return new Promise(function(resolve, reject) {
if (i > 3) {
resolve();
return;
}
window.setTimeout(function() {
console.log(i);
$('#output').append(i).append('<br/>');
i += 1;
pro().then(function() {
resolve()
}).catch(function() {
reject()
});
}, 2000);
});
}
pro().then(function () { $('#output').append("now here"); })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="output"></div>

Categories