node using await in for loop which calls an async function - node.js

I'm having a little trouble getting back a result from an async function I am calling in a for loop, I should be getting back a string but it is returning a promise instead
I am following this syntax but I haven't been able to get it to work properly and I'm hoping for some insight into why this fails https://eslint.org/docs/rules/no-await-in-loop
here's the function I am trying to work with, in this case decodeTheObject is async and returns a promise but if I use await decodeTheObject eslint will give me an error saying I can't use await inside a for loop, unfortunately the workaround above still results in a promise being returned instead of a resolved value
async function decode (request, encodedObj) {
const decodedArr = [];
try{
for (const id of encodedObj) {
decodedArr.push({
data: decodeTheObject(request, id), // this is an async function
partial: true,
});
}
await Promise.all(decodedArr);
return decodedArr;
}catch (err) {
throwError(
req,
{
errorDetails: [
{
issue: 'INVALID_ENCODING',
description: 'Invalid encoded obj',
},
],
},
);
}
};
// ----- calling function -----
const decodedObj = await decode(request, encodedItem);
const payload = {
newprop: decodedObj
};

Promise.all() has to work directly on an array of promises. You are passing it an array of objects which won't know how to reach into the objects to get the promises so thus, it won't accomplish anything useful. There are a couple ways to approach this.
This will await each async call in sequence, get the value and push the value into the array:
async function decode (request, encodedObj) {
const decodedArr = [];
try{
for (const id of encodedObj) {
let data = await decodeTheObject(request, id);
decodedArr.push({data, partial: true});
}
return decodedArr;
} catch(e) {
...
}
}
Or, you could run them in parallel with Promise.all() like this by creating an array of promises and using Promise.all() on that array:
async function decode (request, encodedObj) {
return Promise.all(encodedObj.map(id => {
return decodeTheObject(request, id).then(data => {
return {data, partial: true};
});
})).catch(err => {
...
});
}

Related

How to call a custom function synchronously in node js?

I am developing a server project which needs to call some functions synchronously. Currently I am calling it in asynchronous nature. I found some similar questions on StackOverflow and I can't understand how to apply those solutions to my code. Yet I tried using async/await and ended up with an error The 'await' operator can only be used in an 'async' function
Here is my implementation
function findSuitableRoom(_lecturer, _sessionDay, _sessionTime, _sessionDuration, _sessionType){
let assignedRoom = selectRoomByLevel(preferredRooms, lecturer.level, _sessionDuration); <------- Need to be call synchronously
if (!assignedRoom.success){
let rooms = getRooms(_sessionType); <------- Need to be call synchronously
assignedRoom = assignRoom(_lecturer.rooms, _sessionDuration, _lecturer.level);
} else {
arr_RemovedSessions.push(assignedRoom.removed)
}
return assignedRoom;
}
function getRooms(type){
switch (type){
case 'Tutorial' : type = 'Lecture hall'
break;
case 'Practical' : type = 'Lab'
break;
default : type = 'Lecture hall'
}
Rooms.find({type : type},
(err, rooms) => {
if (!err){
console.log('retrieved rooms ' + rooms)
return rooms;
}
})
}
Here I have provided only two methods because full implementation is very long and I feel if I could understand how to apply synchronous way to one method, I can manage the rest of the methods. Can someone please help me?
Well yes await is only available inside an async function so put async infront of findSuitableRoom.
Also you did a classic mistake. You use return inside of a callback function, and expect getRooms to return you some value.
async function findSuitableRoom(
_lecturer,
_sessionDay,
_sessionTime,
_sessionDuration,
_sessionType
) {
let assignedRoom = selectRoomByLevel(
preferredRooms,
lecturer.level,
_sessionDuration
);
if (!assignedRoom.success) {
try {
let rooms = await getRooms(_sessionType);
} catch (err) {
console.log("no rooms found");
}
assignedRoom = assignRoom(
_lecturer.rooms,
_sessionDuration,
_lecturer.level
);
} else {
arr_RemovedSessions.push(assignedRoom.removed);
}
return assignedRoom;
}
Also wrap it in an try / catch
Since .find() returns an promise if you dont pass an callback you can write it like this
function getRooms(type) {
switch (type) {
case "Tutorial":
type = "Lecture hall";
break;
case "Practical":
type = "Lab";
break;
default:
type = "Lecture hall";
}
return Rooms.find({ type });
}
Note here findSuitableRoom is no longer synchronouse. Its async and returns an promise. That means you will need to use the function like this:
findSuitableRoom.then(res => { console.log(res); })
The 'await' operator can only be used in an 'async' function
This means whenever you want to use the await keyword it needs to be inside a function which has an async keyword (returns promise)
read this https://javascript.info/async-await for more info
const add = (a, b) => {
return new Promise((resolve, reject) => {
setTimeout(() => { //to make it asynchronous
if (a < 0 || b < 0) {
return reject("don't need negative");
}
resolve(a + b);
}, 2000);
});
};
const jatin = async () => {
try{
const sum = await add(10, 5);
const sum2 = await add(sum, -100);
const sum3 = await add(sum2, 1000);
return sum3;
} catch (e) {
console.log(e);
}
};
jatin()
Try this
let's understand this
add is a normal function that does some asynchronous action like
waiting for 2 seconds
normally we use await with async function so in order to use it we make as async function jatin and use await with add function call
to make it synchronous, so until first await add call() doesn't
happen it wont execute another await add call().
Example code if you will use in your app.js
router.post("/users/login", async (req, res) => {
try {
const user = await User.findByCredentials(
req.body.email,
req.body.password
);
const token = await user.generateToken();
res.status(200).send({
user,
token,
});
}
catch (error) {
console.log(error);
res.status(400).send();
}
});

Async - Await issue using Twitter API

I'm currently trying to practice using an API with Twitter API.
I'm using Twit package to connect to twitters API but when I try to do a get request I get
Promise { pending }
I have tried using Async-Await but I'm not sure what I'm doing wrong here.
Here is my code:
const Twit = require('twit');
const twitterAPI = require('../secrets');
//Twit uses OAuth to stablish connection with twitter
let T = new Twit({
consumer_key: twitterAPI.apiKey,
consumer_secret: twitterAPI.apiSecretKey,
access_token: twitterAPI.accessToken,
access_token_secret: twitterAPI.accessTokenSecret
})
const getUsersTweets = async (userName) => {
let params = { screen_name: userName, count: 1 }
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data
})
return userTweets
}
console.log(getUsersTweets('Rainbow6Game'));
Problem
The biggest assumption that is wrong with the sample code is that T.get is expected to eventually resolve with some data.
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data // 'data' returned from here is not necessarily going to be received in 'userTweets' variable
})
callback function provided as last argument in T.get function call doesn't have to be preceded by an 'await'.
'data' returned from callback function is not necessarily going to be received in 'userTweets' variable. It totally depends on how T.get is implemented and can not be controlled.
Reason
Thing to be noted here is that async / await works well with Promise returning functions which eventually get resolved with expected data, however, that is not guaranteed here
Relying on the result of asynchronous T.get function call will probably not work because it returns a Promise { pending } object immediately and will get resolved with no data. The best case scenario is that everything with your function will work but getUsersTweets function will return 'undefined'.
Solution
The best solution is to make sure that your getUsersTweets function returns a promise which eventually gets resolved with correct data. Following changes are suggested:
const getUsersTweets = (userName) => {
return new Promise ((resolve, reject) => {
let params = { screen_name: userName, count: 1 }
T.get('search/tweets', params, function (err, data, response) {
if (err) {
reject(err);
}
resolve(data);
})
}
}
The above function is now guaranteed to return expected data and can be used in the following way:
const printTweets = async () => {
const tweets = await getUsersTweet(userName);
console.log(tweets);
}
printTweets();
From what I can see on your code, getUserTweets is an async function, so it will eventually return a promise. I'm assuming you will use this value on another function, so you will need to use it inside an async function and use await, otherwise you will always get a promise.
const logTweets = async (username) => {
try {
const userTweets = await getUsersTweets(username);
// Do something with the tweets
console.log(userTweets);
catch (err) {
// catch any error
console.log(err);
}
}
If logging is all you want and you wrapped it inside a function in which you console.log it, you can call that function directly:
logTweets('someUsername');

Unable to get async/await working with Axios and array.map()

I'm trying to build a function that maps over a given array and then runs an axios call on each item. Once that is done, I want to return the mapped array for use in another function.
Here is the code:
require('dotenv').config();
const axios = require('axios');
const consola = require('consola');
function getLeagues(listItems) {
const today = new Date();
const season = today.getMonth() >= 6 ? today.getFullYear() : today.getFullYear() - 1; // this gets the game ready for the new season every July 1st
// we will end up directly returning listItems.map once the issue is solved
const selectedLeagues = listItems.map(async (item) => {
const countryName = item.country;
const leagueName = item.league;
try {
const response = await axios({
method: 'GET',
url: `https://api-football-v1.p.rapidapi.com/v2/leagues/country/${countryName}/${season}`,
headers: {
'content-type': 'application/octet-stream',
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'x-rapidapi-key': process.env.FOOTY_API_KEY,
},
});
const leagueData = response.data.api.leagues
.filter((league) => league.name === leagueName)
.map((data) => {
return {
leagueId: data.league_id,
name: data.name,
seasonStart: data.season_start,
seasonEnd: data.season_end,
country: data.country,
};
})
.pop(); // we use pop() because filter() and map() return arrays and we don't want an array of 1 object, just that object
consola.ready({
// this displays all of the data as needed
// this also runs after the below consola block
message: `leagueData: ${JSON.stringify(leagueData, null, 2)}`,
badge: true,
});
return leagueData;
} catch (error) {
throw new Error(error);
}
});
consola.ready({
// this displays an array with an empty object, not an array with above leagueData object
// this also runs before the above consola block
message: `selectedLeagues: ${JSON.stringify(selectedLeagues, null, 2)}`,
badge: true,
});
return selectedLeagues;
}
module.exports = getLeagues;
I'm not sure why the selectedLeagues array is being returned before the leagueData object is even ready. I thought async/await held everything. Instead, in my console, I am getting:
selectedLeagues: [
{}
]
leagueData: {
"leagueId": 753,
"name": "Liga 3",
"seasonStart": "2019-07-19",
"seasonEnd": "2020-05-16",
"country": "Germany"
}
What am I doing wrong?
You have to wrap your listItems.map in a promise all function because map on its own isn't compatible with async.
// Now magically you can add async to your map function...
Promise.all(listItems.map(async item => {
// Then put your try catch here so that it only wraps around
// the results of the function you're awaiting...
let response
try {
response = await axios()
} catch (err) {
return err;
}
// Anything else you want to do with the response...
return response
})).then(results => {
// All the resolved promises returned from the map function.
console.log(results)
})
When you use the await keyword inside an async function the rest of the code will just wait for the result of the awaited function, the try catch part is to catch any error you might get that's out of your control which is why you only try catch around the awaited function.
If you wrap too much of your code inside a try catch you won't be able to diagnose and handle the error properly.
You could put a try catch around the whole of your code if you wanted but the problem would be that the whole code would error out whenever you have any kind of small problem.
You can also do this with a for of loop which might look a bit cleaner...
for await (let item of listItems) {
// try catch await axios etc...
}
You can use async together with a Promise,
const arr = [1, 2, 3];
const asyncRes = await Promise.all(arr.map(async (i) => {
await sleep(10);
return i + 1;
}));
console.log(asyncRes);
// 2,3,4
What's happening in your .map() is async, not what's outside of it. The .map() kicks off but it doesn't block the consola.ready({ at the bottom.

NodeJS Async / Await - Build configuration file with API call

I would like to have a configuration file with variables set with data I fetch from an API.
I think I must use async and await features to do so, otherwise my variable would stay undefined.
But I don't know how to integrate this and keep the node exports.myVariable = myData available within an async function ?
Below is the code I tried to write to do so (all in the same file) :
const fetchAPI = function(jsonQuery) {
return new Promise(function (resolve, reject) {
var reqOptions = {
headers: apiHeaders,
json:jsonQuery,
}
request.post(apiURL, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
}
var wallsData = {}
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
let body = await utils.fetchAPI(jsonQuery)
let pageList = await body[0].dataHashes
for(i=0;i<pageList.length;i++){
var page = pageList[i]
wallsData[page.title.fr] = [page.difficultyList,page.wallType]
}
return wallsData
throw new Error("WOOPS")
}
try{
const wallsData = fetchWalls()
console.log(wallsData)
exports.wallsData = wallsData
}catch(err){
console.log(err)
}
The output of console.log(wallsData) shows Promise { <pending> }, therefore it is not resolved and the configuration file keep being executed without the data in wallsData...
What do I miss ?
Thanks,
Cheers
A promise is a special object that either succeeds with a result or fails with a rejection. The async-await-syntax is syntactic sugar to help to deal with promises.
If you define a function as aync it always will return a promise.
Even a function like that reads like
const foo = async() => {
return "hello";
}
returns a promise of a string, not only a string. And you need to wait until it's been resolved or rejected.
It's analogue to:
const foo = async() => {
return Promise.resolve("Hello");
}
or:
const foo = async() => {
return new Promise(resolve => resolve("Hello"));
}
Your fetchWalls similarly is a promise that will remain pending for a time. You'll have to make sure it either succeeds or fails by setting up the then or catch handlers in your outer scope:
fetchWalls()
.then(console.log)
.catch(console.error);
The outer scope is never async, so you cannot use await there. You can only use await inside other async functions.
I would also not use your try-catch for that outer scope promise handling. I think you are confusing the try-catch approach that is intended to be used within async functions, as there it helps to avoid nesting and reads like synchronous code:
E.g. you could do inside your fetchWalls defintion:
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
try {
let body = await utils.fetchAPI(jsonQuery)
} catch(e) {
// e is the reason of the promise rejection if you want to decide what to do based on it. If you would not catch it, the rejection would chain through to the first error handler.
}
...
}
Can you change the statements like,
try{
const wallsData = fetchWalls();
wallsData.then((result) => {
console.log(result);
});
exports.wallsData = wallsData; // when importing in other file this returns as promise and we should use async/await to handle this.
}catch(err){
console.log(err)
}

nodejs promisify a callback function with one parameter /callback

util.promisify appears to always expect 2 parameters from a callback function, however the existing functions do not have a seperate callback for err and data, but only a single callback.
How can I handle this ???
const {promisify} = require('util');
function y(text,cb){
setTimeout(function(){cb({error:false,data:text})},1000);
}
async function test(text) {
try{
const z = promisify(y)
return await z(text);
} catch(e) {return {error:true,msg:e}}
}
console.log(test('xxx'));
What I am looking for is to return the value from function y syncronously and not getting a promise i.e.
var x = test('xxx');
Given the information in your comment, you can wrap the function with a compatible signature to be passed directly to promisify():
const { promisify } = require('util')
function y (query, callback) {
callback(query)
}
function yCompatible (query, callback) {
y(query, ({ error, data }) => {
callback(error && data, error || data)
})
}
const yAsync = promisify(yCompatible)
async function test (query) {
try {
return yAsync(query)
} catch (error) {
return error
}
}
test('xxx').then(
data => { console.log(data) },
error => { console.error(error) }
)
Also try not to get in the habit of using single letter variables like a mathematician ;) I realize this is just example code, but even then it's helpful to be a bit more explicit about your intent.

Resources