How to chain Promises while keeping the data? - javascript

I have two API-calls chained like so:
let weatherPromise;
if (crds) {
weatherPromise = mapbox.getLocation(crds)
.then(locData => darksky.getWeather(crds))
.then(weatherData => Object.assign(weatherData, {city: locData.city}));
} else if (term) {
weatherPromise = mapbox.getCoords(term)
.then(locData => darksky.getWeather(locData.crds));
} else {
weatherPromise = Promise.reject({error: 'Aborted due to unexpected POST-Body'});
}
weatherPromise.then(weatherData => {
io.emit('update', weatherData);
console.log(`Answer sent: ${JSON.stringify(weatherData)}`);
}, reject => {
io.emit('update', reject);
console.error(reject.error);
});
But it is not working at all. The error-handling is all over the place (mainly just logging undefined and failing) and for example locData doesn't seem to be accessible in that second then() anymore. I tried nesting Promises and it worked fine but I would like to avoid that anti-pattern if I can.

You can nest `.thens´, but in this case, I'd clearly go with async / await:
async function retrieveWeather() {
if (crds) {
const locData = await mapbox.getLocation(crds);
const weatherData = await darksky.getWeather(crds);
return Object.assign(weatherData, {city: locData.city});
} else if (term) {
const locData = await mapbox.getCoords(term);
const weatherData = await darksky.getWeather(locData.crds);
return weatherData;
} else {
throw {error: 'Aborted due to unexpected POST-Body'};
}
}
(async function sendWeather() {
try {
const weatherData = await retrieveWeather();
io.emit('update', weatherData);
console.log(`Answer sent: ${JSON.stringify(weatherData)}`);
} catch(error) {
io.emit('update', error);
console.error(reject.error);
}
})();

Related

Promisify a curried (higher order function) callback

I am trying to promisify (or leverage async await feature) the callback in the function getId, using new Promise(resolve => ..., but because of the usage of the higher order function getAddresses, I am a bit stumped. I am not great at functional programming. Any suggestions on how do I promisify this one?
const {queryRecord, getData} = require(“#xyzLib”);
const getId = (callback) => {
getData(“attr1”,”attr2”,getAddresses(callback));
}
const getAddresses = (callback) => (result) => {
if (!result.success) {
callback(new Error(‘Exception details’))
} else {
queryRecord(objectName, (result) => {
callback(null, result.name);
});
}
}
// invoking call
getId(async (error, zip, state) => {
if (error) {
console.log(error.message)
} else {
await fetch(encodeURI(settingsUrl), {
method: 'GET',
});
....
Since getId() accepts a callback as the last argument and that callback uses the nodejs calling convention, you can just directly use util.promisify() on getId like this:
const { promisify } = require('util');
const getIdPromise = promisify(getId);
getIdPromise().then(result => {
console.log(result);
let fetchResult = await fetch(...);
...
}).catch(err => {
console.log(err);
});
Or, if you're already inside an async function body:
try {
const result = await getIdPromise();
console.log(result);
const fetchResult = await fetch(...);
...
} catch (err) {
console.log(err);
}
You should promisify on the lowest level available, i.e. the imported functions from the library. Then you don't even need that currying. With
const { promisify } = require('util');
const xyz = require(“#xyzLib”);
const queryRecord = promisify(xyz.queryRecord);
const getData = promisify(xyz.getData);
you can write simply
async function getId() {
return getAddresses(await getData("attr1", "attr2"));
}
async function getAddresses(data) {
if (!data.success) throw new Error("Exception details");
const result = await queryRecord(objectName);
return result.name;
}
// invoking call
try {
const zip = getId();
await fetch(encodeURI(settingsUrl), {
method: 'GET',
});
} catch(error) {
console.log(error.message);
}

How to wait for all promises to resolve

I have two api call.
I want to do some calculation based on the results of both api.
I am using Promise.all() for waiting for both promises to resolve.
const getHashTagList = async () => {
loader.start();
try {
await getAllHashTags().then((response: any) => {
setHashtagList([...response?.data]);
});
} catch (err) {
} finally {
loader.stop();
}
};
const getUserFollowingHT = async () => {
loader.start();
try {
await getUserDetails().then((response: any) => {
setUserFollowingHT([...response?.data?.followingHashtags]);
});
} catch (err) {
} finally {
loader.stop();
}
};
For calling these 2 promises I am using below syntax:
useEffect(() => {
//getHashTagList();
// getUserFollowingHT();
Promise.all([getHashTagList, getUserFollowingHT]).then(
(combineResp) => {
console.log(combineResp);
}
);
}, []);
But in the output I am getting function declaration syntax.
It is not able to get call those promises.
Try this
useEffect(() => {
(async () => {
const values = await Promise.all([getHashTagList, getUserFollowingHT]);
console.log(values);
})();
}, []);

Problem with .push with Asynchronous function

The Problem is with the uplines.push.
I always get an empty uplines array so the last part of the code doesn't run. The promises resolve later and I get the correct data. May I know how to go about doing it the correct way?
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
uplines = uplines.filter(
(v, i) => uplines.findIndex((index) => index === v) === i
); //remove duplicates
uplines.forEach((user) => {
if (user.chatId) {
sendTelegramMessage(user.chatId, saleToDisplay, currentUser.displayName);
console.log("Telegram Message Sent to " + user.displayName);
} else {
console.log(user.displayName + " has no chatId");
}
});
There are a few things that you have missed out while implementing the async call, which are explained in the inline comments in the code snippet.
A short explanation for what happened in your code is that in the line sale.rens.forEach you are passing an async function in the argument, which does not make any difference to the function forEach, it will execute it without waiting for it to complete.
Therefore in my answer I am using Promise.all to wait for all the async function calls to complete before returning the result.
// This is wrapped in an immediately executed async function because await in root is not supported here
(async () => {
const mockGetData = () => new Promise(resolve => setTimeout(resolve, 1000));
const sale = {
rens: [
{ userFid: 1 },
{ userFid: 2 },
{ userFid: 3 }
]
};
const getAllUplines = async () => {
const uplines = [];
const findUser = async (userFid) => {
// Simulating an async function call
const userDoc = await mockGetData();
console.log("User data received");
uplines.push(`User ${userFid}`);
};
const promises = [];
sale.rens.forEach(ren => { // This function in foreach does not have to be declared as async
// The function findUser is an async function, which returns a promise, so we have to keep track of all the promises returned to be used later
promises.push(findUser(ren.userFid));
});
await Promise.all(promises);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
})();
In order to get the results of getAllUplines() properly, you need to add await to all async functions called in getAllUplines().
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
await findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
await findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};

Restart then() block when catch is returned an error

I want to restart my code when catch function is returned an error.
let ig = require('instagram-scraping')
module.exports.instagram = async (req, res) => {
ig.scrapeTag('postthisonmypersonalblogtoo').then(async result => {
let myPostCodes = [];
result.medias.forEach(content => {
if(content.owner_id == '10601516006'){
myPostCodes.push(content.shortcode);
}
})
await res.render('instagram', {
myPosts : myPostCodes
});
}).catch((err) => {
console.error(err);
// if(err){
//ig.scrapeTag('postthisonmypersonalblogtoo').then(async result => { ... } //do same things as above
}
})
}
The reason that i wanted to do this: sometimes ig.scrapeTag method find my posts but sometimes cant find anything and return me that;
Error: Error scraping tag page "postthisonmypersonalblogtoo"
at Request._callback (C:\Users\Byte\Desktop\BLOG\node_modules\instagram-scraping\index.js:114:24)
at Request.self.callback (C:\Users\Byte\Desktop\BLOG\node_modules\request\request.js:185:22)
So when i take this error i want to reset ig.scrapeTag to research my posts on instagram again
(By the way sorry for my bad English and if you guys have a advice for another instagram api let me know (api can be official or unofficial doesn't matter)
I would move scrape functionality into separate function and introduce retry counter to keep track of number of retries. Also not sure why you mix and match then/catch with async/await. I think it is more readable and consistent to use async/await everywhere. Something like this:
let ig = require('instagram-scraping')
const MAX_RETRY_COUNT = 2;
async function scrapeInstagram() {
let retryCount = 0;
const scrapeTag = async () => {
try {
const result = await ig.scrapeTag('postthisonmypersonalblogtoo');
return result;
}
catch(e) {
if (retryCount < MAX_RETRY_COUNT) {
retryCount++;
scrapeTag();
} else {
throw e;
}
}
}
const result = await scrapeTag();
let myPostCodes = [];
result.medias.forEach(content => {
if(content.owner_id == '10601516006'){
myPostCodes.push(content.shortcode);
}
});
return myPostCodes;
}
module.exports.instagram = async (req, res) => {
try {
const myPostCodes = await scrapeInstagram();
await res.render('instagram', {
myPosts : myPostCodes
});
}
catch(e) {
console.log(e);
res.status(500).send("Could not load from Instagram")
}
}

Multiple try-catch blocks inside async function

I have an async function where there is some operations like this:
async function myAsyncFunction(argument) {
let arrayTasks = [
{
const run = async () => {
let response = await getDataAsync();
}
},
{
const run = async () => {
let response = await getDataAsync();
}
}
]
for (const index in arrayTasks) {
await arrayTasks[index].run();
}
}
The function above is a simplified version of my code, it works. But I am not sure where do I need to put try-catch block:
Wrapping all content function:
async function myAsyncFunction(argument) {
try{
// All code
}catch (e) {
// catch code
}
}
Or inside my asyncs functions and for operator:
async function myAsyncFunction(argument) {
let arrayTasks = [
{
const run = async () => {
try{
let response = await getDataAsync();
}catch (e) {
// catch code
}
}
},
{
const run = async () => {
try{
let response = await getDataAsync();
}catch (e) {
// catch code
}
}
}
]
for (const index in arrayTasks) {
try{
await arrayTasks[index].run();
}catch (e) {
// catch code
}
}
}
What is a correct way?
The arrayTasks variable is dynamic length on my original code.
Depends how and where you want to handle failure.
One approach to "an array of asynchronous tasks whose execution may fail" is a pattern like this:
async function myAsyncFunction(argument) {
const success = async res => ({ success: await res }) // Promise<{success: res}>
const error = async err => ({ error: await err }) // Promise<{error: e}>
const arrayTasks = [
{
run: async () => getDataAsync()
}
},
{
run: async () => getDataAsync()
}
]
const runWithResult = task => task.run().then(success).catch(error)
const outcomes = await Promise.all(arrayTasks.map(runWithResult))
console.log(outcomes) // array of {error: e} | {success: res}
}
You can chain a .catch() handler on an async function, and it has the same effect as wrapping it in try/catch.
More here, if you are interested. The section "Refactor without fp-ts" shows how to reduce that array from [{error: e} | {success: res}] to {error: [], success: []}, which is way easier to work with:
const { error, success } = outcomes.reduce((acc, o) => o.error ?
{ error: [...acc.error, o.error], success: acc.success } :
{ error: acc.error, success: [...acc.success, o.success] },
{ error: [], success: [] })
This is an FP type called Either - an operation may return "either" (in this case) a value or an error.
Your code doesn't throw with this approach.
If you know that something may fail, it's not exceptional. Exceptions are when some unexpected failure occurs, IMHO.
"Tasks that may fail" that are known ahead of time just need the error path code written.
If you take this approach, I recommend building it as a first-class state reducing machine, like this:
// Takes an array of async tasks that may throw of shape {run: () => Promise<result>}
// Returns Promise<{error: error[], success: result[]}>
async function executeAsyncTasks(arrayOfAsyncTasks) {
const success = async res => ({ success: await res }) // Promise<{success: res}>
const error = async err => ({ error: await err }) // Promise<{error: e}>
const runWithResult = task => task.run().then(success).catch(error)
const outcomes = await Promise.all(arrayOfAsyncTasks.map(runWithResult))
const outcomesFlat = outcomes.reduce((acc, o) => o.error ?
{ error: [...acc.error, o.error], success: acc.success } :
{ error: acc.error, success: [...acc.success, o.success] },
{ error: [], success: [] })
return outcomesFlat
}
It all depends... My rule of thumb is only catch something if you intend on doing something with it, i.e some error handling. If the handling of the error is going to be the same across all cases, wrap the it all with one try/catch, else wrap individual cases, with their own error handling code.

Categories

Resources