nodejs- Best method to perform multiple async calls inside a function? - node.js

I'm creating an API using Express JS (Express 4) framework. I'm pretty new to NodeJS so I would like to know the best way to perform multiple async calls inside the same function.
For this example, I've got a function called Login in my Login Controller. Inside this function, let's say I gotta make quite a few async calls for authenticating a user, saving login info and similar functionalities like that.
But for some async calls, data to be processed should be fetched from the previous async call (they are dependent functions) and some functions are not like that.
Right now, this is how I'm doing the calls.
exports.login = function (req, res) {
var user = {
email: 'fakeemail#id.com',
password: 'password'
};
//Async call 1
Login.authUser(user, function (err1, rows1) {
var user_id = rows1[0].id;
//Async call 2 (depends on async call 1 for user_id)
Login.fetchUserDetails(user_id, function (err2, rows2) {
//Async call 3
Login.updateLoginInfo(param3, function (err3, rows3) {
//Some functionality occurs here then async call 4 happens
//Async call 4
Login.someOtherFunctionality(param4, function (err4, rows4) {
//return response to user
res.json({
success: true
});
});
});
});
});
};
Now all these async calls are nested. Is there any other way I can do this?
P.S: I didnt add error handling in this example

you can use promise as well. It will make you syntax more pretty. Your code will look like
Login.authUser(user).
then(fetchUser).
then(updateLoginInfo).
then(someOtherFunctionality).
catch(function(error){
//log your error or whatever
});

You can use Promise as suggested by Shahzeb.
Promises are objects that get resolved in future (asynchronous). Once a Promise is completed it either resolves or rejects. All promises resolved are then ed and those get rejected are catch ed
pseudo code
let myPromise = function() {
return new Promise(function(resolve, reject) {
resolve('foo');
});
};
myPromise().then( (v) => {
console.log(v);
})

Use Promise Chaining
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
More details refer Promise Chaining and chain promises in javascript

Using async/await (Requires Node.js v7.6)
This strategy also uses promises. In my opinion it improves readability since you are refactoring out each individual promise call into separate methods.
Codepen
// mock async calls
const authUser = user => Promise.resolve([{ id: 1 }]); // returns users id
const fetchUserDetails = user_id => Promise.resolve({ name: 'Fred', age: '10000' }); // returns users details
const updateLoginInfo = param3 => Promise.resolve({ status: 'success' }); // returns success?
const someOtherFunctionality = param3 => Promise.resolve({ field: 'value' }); // returns something
// all async functions return a promise
const login = async (/*req, res*/) => {
// User object
const user = {
email: 'fakeemail#id.com',
password: 'password'
};
// Async call 1
console.log(`Authorizing user...`);
const rows1 = await authUser(user);
const user_id = rows1[0].id;
console.log(`User ${user_id} authorized.`);
// Async call 2 (depends on async call 1 for user_id)
console.log(`Fetching user detail...`);
const rows2 = await fetchUserDetails(user_id);
console.log(`User Detail was fetched: ${JSON.stringify(rows2)}`);
// Async call 3
console.log(`Updating login info...`);
const param3 = `something`;
const rows3 = await updateLoginInfo(param3);
console.log(`Login info was successful: ${JSON.stringify(rows3)}`);
// Some functionality occurs here then async call 4 happens
console.log(`\nDoing stuff after async call 3, but before async call 4....\n`);
// Async call 4
console.log(`Async call 4...`);
const param4 = `something`;
const rows4 = await someOtherFunctionality(param4);
console.log(`END OF LOGIN FUNCTION`);
return 'returned value';
}
// run the async function
login()
.then(result => {
// respond
// res.json({ success: true });
console.log(`Promise value: ${result}`);
console.log(`Response: { success: true }`);
})
.catch(err => {
console.log(err);
})

Related

node using await in for loop which calls an async function

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 => {
...
});
}

Jest done callback is not assignable to parameter of type ProvidesCallback or undefined

I am trying to create a test with jest and I want to use done() callback but Typescript is not accepting it, I tried to use type any, jest.DoneCallback or leaving it without any type but again is not working. Any solution or idea?
it('implements optimistic concurrency control', async (done: any) => {
const ticket = Ticket.build({
title: 'Concert 123423',
price: 5,
userId: '123'
});
await ticket.save();
const firstInstance = await Ticket.findById(ticket.id);
const secondInstance = await Ticket.findById(ticket.id);
firstInstance!.set({ price: 10 });
secondInstance!.set({ price: 15 });
await firstInstance!.save();
try {
await secondInstance!.save();
} catch (err) {
console.log(err);
return done();
}
throw new Error('Should not reach this point');
});
You choose either to use Promise or a callback. In your case, you want to use async/await because you are waiting for the save() methods before continuing in the flow.
Your code updated:
it('implements optimistic concurrency control', async () => {
const ticket = Ticket.build({
title: 'Concert 123423',
price: 5,
userId: '123'
});
await ticket.save();
const firstInstance = await Ticket.findById(ticket.id);
const secondInstance = await Ticket.findById(ticket.id);
firstInstance!.set({ price: 10 });
secondInstance!.set({ price: 15 });
await firstInstance!.save();
try {
await secondInstance!.save();
} catch (err) {
console.log(err);
return;
}
throw new Error('Should not reach this point');
});
Here is an example of using the callback:
it('some asynchronous test', (done) => {
request(app)
.get('/endpoint')
.expect(200)
.expect((res) => {
// insert some code stuff here
})
// The callback function is passed in so it will call
// it once it finishes the asynchronous code
.end(done)
})
Also, you shouldn't have to specify the type for the callback, it should
be inferred. You can if you want though.
Just remove the async keyword in your first line.
The second argument of the it function is of type ProvidesCallback, defined as:
// #types/jest/index.d.ts
type ProvidesCallback =
| ((cb: DoneCallback) => void | undefined)
| (() => Promise<unknown>)
so that means if you make the second parameter an async function, typescript will think it is the second one: () => Promise<unknown>. The first one is what you want, since you want to make use of the DoneCallback.

Why does Async firebase fetching is not working? (NODE JS)

Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

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');

Node.js exporting a function that returns an object

I am trying to export a function I defined in a file (chat.js) and use it in another file (main.js).
This is the code for chat.js
module.exports.list = function() {
var chats;
sequelize.query('SELECT * FROM Chats', {model: Chats}).then((chat) =>{
console.log(chat);
chats = chat
})
return chat;
};
When I use it in main.js
chat.list();
It works as expected.
However I get undefined when I store the return object to a variable and console.log it
You are returning chat object in synchronous way but its asynchronous query processing.
Use promise chaining or async/await.
module.exports.list = function () {
var chats;
return new Promise((resolve, reject) => {
sequelize
.query('SELECT * FROM Chats', {model: Chats})
.then((chat) => {
console.log(chat);
resolve(chat);
})
})
};
somewhere :
function async test(){
let result = await chat.list();
console.log(result);
}

Resources