Using async/await to return data in hapi.js's handler function - node.js

I want to return dataSet from my handler function. However it's nested inside my promise chain. I'm attempting to use await/async but the value of data is still undefined. Thoughts on how to do this?
handler: (request, h) => {
let data: any;
connection.connect((err) => {
if (err) {
console.error("Error-------> " + err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
.then(async dataSet => {
console.log(dataSet);
data = await dataSet;
});
});
return data;
}
Err is not being thrown since logging to the console prints out the values I'm looking for.

In order to do this you would need to make handler return a Promise, and within the handler, wrap the connection.connect block with a Promise.
e.g.
handler: (request, h) => {
// wrap connector.connect(...) in a Promise
return Promise<any>((resolve, reject) => {
connection.connect(err => {
if (err) {
console.error("Error -----> ", err);
// error in connection, propagate error via reject
// and do not continue processing
return reject(err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
// don't think you need this to be async
// as connector.getAllEvents() will should return a Promise<T>
// and .then() is like a .map() so its first argument is a T
// rather than a Promise<T>
.then(dataSet => {
console.log(dataSet);
// we finally have our value
// so we propagate it via resolve()
resolve(dataSet);
});
});
});
}

Data is not initialized when you return it. You can test it by adding another log statement just before return, you'll see it prints before console.log(dataSet);
I don't know what connection.connect returns (what framework is it?), but you can promisify it. Then you either return a promise to "connect and get the data" and let the caller wait on it, or you await on it inside your function and return the data after promise is fulfilled.

Related

Need to confirm how callbacks and errors work in async.each with try-catch blocks

I've been tinkering with this for a few days now and have seen a number of different patterns. In some ways I feel more confused than I did when I began!
itemsArr is a list of item objects (itemObj) with summary information about each item. Each itemObj contains an itemId which doubles as the API slug directory. So, I need to iterate through the itemsArr, make an API call for each item, and return the updated array with all of the details that were retrieved from each API call. When this is finished, I want to log the enriched array, enrichedItemsArr to persistant storage.
It does not matter in what order the API calls return, hence using async.each. I also don't want to interrupt the execution if an error occurs. My questions:
'Done enriching array' is printing before execution of enrichArr() -> why is await async.each... in enrichArr() not blocking?
I am getting TypeError: callback is not a function in the inner try-catch. Not sure why.
If I pass err to callback() in the inner try-catch, will that halt execution?
Should I pass itemsArr to processDone as the 2nd argument? Is there a way to return itemsArr to main() from the processDone() method?
Does err passed to the final callback contain an array of errors?
const main = async () => {
const itemsArr = items.getArr(); // --> retrieves locally cached itemsArr
const enrichedItemsArr = await enrichArr(itemsArr); // --> handling the async iterator stuff below
await logToDB(enrichedItemsArr); // --> helper function to log enriched info to database
console.log('Done enriching array');
};
const enrichArr = async (itemsArr) => {
// Outer try-catch
try {
const processItem = async (item, callback) => {
// Inner try-catch
try {
const res = await doSomethingAsync(itemID);
item.res = res;
callback(); // --> currently getting `TypeError: callback is not a function`
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
const processDone = (err, itemsArr) => {
if (err) console.error(err); // --> Is err an array of errors or something?
return itemsArr; // --> how do I return this to main()?
};
await async.each(itemsArr, processItem, processDone);
} catch (err) {
throw err; // --> if async.each errors, throw
}
};
Hope this is a good answer for you.
why is await async.each... in enrichArr() not blocking?
Based on the docs, the async.each will return a promise only if the callback is omitted
each
You're including the callback, the async.each won't return a promise and won't block your code using async/await
I am getting TypeError: callback is not a function in the inner try-catch. Not sure why.
Your processItem should be a plain function, doing that I was able to use callback, seems that the library is not happy when you use async functions
const processItem = (item, callback) => {
const itemId = item;
// Inner try-catch
try {
doSomethingAsync((res) => {
item.res = res;
callback()
});
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
If I pass err to callback() in the inner try-catch, will that halt execution?
Yes, it will throw an error
Should I pass itemsArr to processDone as the 2nd argument? Is there a way to return itemsArr to main() from the processDone() method?
If you want to let know the main method that needs to wait, you won't be able to use processDone.
ItemsArr is an object, you can mutate the object and the main method should be able to see those changes, there is no other way if you want to use array.each.
Maybe there is another method in the async library that allows you to return a new array.
Maybe Map is a good option map
Does err passed to the final callback contain an array of errors?
No, it's a way to let the library know that needs to throw an error.
I created a snippet to allow you to play with the code
const async = require('async');
const logToDB = async (items) => {
items.forEach((item) => console.log(JSON.stringify(item)))
}
const doSomethingAsync = (callback) => {
setTimeout(() => {
console.log('processing data')
callback()
}, 1000);
}
const main = async () => {
const itemsArr = [
{
itemId: '71b13422-2582-4975-93c9-447b66764daf'
},
// {
// errorFlag: true
// },
{
itemId: '8ad24197-7d30-4514-bf00-8068e216e90c'
}
]; // --> retrieves locally cached itemsArr
const enrichedItemsArr = await enrichArr(itemsArr); // --> handling the async iterator stuff below
await logToDB(enrichedItemsArr); // --> helper function to log enriched info to database
console.log('Done enriching array');
};
const enrichArr = async (itemsArr) => {
// Outer try-catch
try {
const processItem = (item, callback) => {
console.log('item: ', item);
const itemId = item;
// Inner try-catch
try {
if (item.errorFlag) {
return callback('Test error');
}
doSomethingAsync((res) => {
item.res = res;
callback()
});
} catch (err) {
item.err = err;
callback(err); // --> not 100% sure what passing err here does...
}
};
await async.each(itemsArr, processItem);
return itemsArr;
} catch (err) {
console.log('Error occurred');
throw err; // --> if async.each errors, throw
}
};
main();

Node Js loop in Promise All not waiting to get request from SOAP

Hi I try to use for for loop data and send request to soap api. The problem is my program seems like it didn't wait to get request from soap request
here is my example data
data = [
{
store: "store1",
product: "product2",
},
{
store: "store2",
product: "product3",
}
]
exports.thisIsFunc = async (req, res, next) => {
Promise.all(data).then(result => {
for (let item of result) {
i++
console.log(i)
args.CustomerCode = item.store
args.ItemNo = item.product
const getApi = apiSend()
}
});
}
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
client.getDataFromAPI(args, (err, result) => {
console.log(result)
return result
})
});
});
}
as you see I try to use new Promise in sendApi function but sometimes It stop the error show up
TypeError: Cannot read property 'getDataFromAPI' of undefined
sometimes it return response from api. The reason that I didn't use async,await in soap because I try to change soap function into async function but it didn't work.
apiSend() has the following issues:
You ignore both callback errors.
You never resolve() or reject() the promise you return.
You can fix it like this:
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
if (err) return reject(err);
client.getDataFromAPI(args, (err, result) => {
if (err) return reject(err);
console.log(result)
resolve(result);
})
});
});
}
Then, in thisIsFunc() you have a number of issues:
There's no point in using Promise.all() on a static array of values. It only does something useful if you pass it an array with at least one promise in it.
There's no declaration of i or args
You don't do anything with the promise returns from apiSend()
It's not really clear what you're trying to do here, but if you want thisIsFunc() to return a promise that resolves with an array of results from all the calls to apiSend(), that could be structured like this:
exports.thisIsFunc = () => {
return Promise.all(data.map(item => {
let args = {
CustomerCode: item.store,
ItemNo: item.product
};
return apiSend(args);
}));
}
This implementation uses data.map() to iterate the array and create an array of promise from calling apiSend(). It then uses Promise.all() to collect all the results from the array of promises into an array of results that is the resolved value of the single returned promise.
It appears you were attempting to declare thisIsFunc() as an Express request handler. You can do that, but then you will need to complete the request inside that function by sending a response for both success and error conditions. As I've shown it above, this is a helper function that retrieves a set of data and then returns a promise that resolves to an array of results. You can use that in a request handler as needed or you can add more code to this to make it into a request handler that sends a response to the incoming request and returns errors responses appropriately.

Promises seem to not be called in correct order?

I am trying to rewrite a module I wrote that seeds a MongoDB database. It was originally working fine with callbacks, but I want to move to Promises. However, the execution and results don't seem to make any sense.
There are three general functions in a Seeder object:
// functions will be renamed
Seeder.prototype.connectPromise = function (url, opts) {
return new Promise((resolve,reject) => {
try {
mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
resolve(connected)
})
} catch (error) {
reject(error)
}
})
}
[...]
Seeder.prototype.seedDataPromise = function (data) {
return new Promise((resolve,reject) => {
if (!this.connected) reject(new Error('Not connected to MongoDB'))
// Stores all promises to be resolved
var promises = []
// Fetch the model via its name string from mongoose
const Model = mongoose.model(data.model)
// For each object in the 'documents' field of the main object
data.documents.forEach((item) => {
// generates a Promise for a single item insertion.
promises.push(promise(Model, item))
})
// Fulfil each Promise in parallel
Promise.all(promises).then(resolve(true)).catch((e)=>{
reject(e)
})
})
}
[...]
Seeder.prototype.disconnect = function () {
mongoose.disconnect()
this.connected = false
this.listeners.forEach((l) => {
if (l.cause == 'onDisconnect') l.effect()
})
}
There is no issue with the main logic of the code. I can get it to seed the data correctly. However, when using Promises, the database is disconnected before anything else is every done, despite the disconnect function being called .finally().
I am running these functions like this:
Seeder.addListener('onConnect', function onConnect () { console.log('Connected') })
Seeder.addListener('onDisconnect', function onDisconnect () {console.log('Disconnected')})
Seeder.connectPromise(mongoURI, options).then(
Seeder.seedDataPromise(data)
).catch((error) => { <-- I am catching the error, why is it saying its unhandled?
console.error(error)
}).finally(Seeder.disconnect())
The output is this:
Disconnected
(node:14688) UnhandledPromiseRejectionWarning: Error: Not connected to MongoDB
at Promise (C:\Users\johnn\Documents\Code\node projects\mongoose-seeder\seed.js:83:37)
which frankly doesn't make sense to me, as on the line pointed out in the stack trace I call reject(). And this rejection is handled, because I have a catch statement as shown above. Further, I can't understand why the database never even has a chance to connect, given the finally() block should be called last.
The solution was to return the Promise.all call, in addition to other suggestions.
You are passing the wrong argument to then and finally. First here:
Seeder.connectPromise(mongoURI, options).then(
Seeder.seedDataPromise(data)
)
Instead of passing a callback function to then, you actually execute the function on the spot (so without waiting for the promise to resolve and trigger the then callback -- which is not a callback).
You should do:
Seeder.connectPromise(mongoURI, options).then(
() => Seeder.seedDataPromise(data)
)
A similar error is made here:
finally(Seeder.disconnect())
It should be:
finally(() => Seeder.disconnect())
Promise Constructor Anti-Pattern
Not related to your question, but you are implementing an antipattern, by creating new promises with new Promise, when in fact you already get promises from using the mongodb API.
For instance, you do this here:
Seeder.prototype.connectPromise = function (url, opts) {
return new Promise((resolve,reject) => {
try {
mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
resolve(connected)
})
} catch (error) {
reject(error)
}
})
}
But the wrapping promise, created with new is just a wrapper that adds nothing useful. Just write:
Seeder.prototype.connectPromise = function (url, opts) {
return mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
return connected;
});
}
The same happens in your next prototype function. I'll leave it to you to apply a similar simplification there, so avoiding the promise constructor antipattern.
In the later edit to your question, you included this change, but you did not return a promise in that function. Add return here:
return Promise.all(promises).then(() => {
//^^^^^^
return true
}).catch(() => {
console.log(`Connected:\t${this.connected}`)
})

Proper way to run asynchronous function in a Promise

I am making a test app using systeminformation. I'm trying to make it so that each then waits for the previous function to finish. The problem I'm having is that the functions I am running inside are also promises, so the next then runs before the function finishes.
const si = require('systeminformation');
var cpuObj;
function initCPU() {
return new Promise(resolve => {
si.cpu()
.then(data => cpuObj = data)
.catch(err => console.log(err))
.then(() => {
setTimeout(() => console.log("timer"), 3000);
})
.then(() => {
si.cpuTemperature().then(data => console.log(data));
})
.then(() => {
console.log("here");
});
});
}
function test() {
console.log(cpuObj);
}
initCPU().then(() => {
test();
});
Output:
here
{ main: -1, cores: [], max: -1 }
timer
Expected Output:
{ main: -1, cores: [], max: -1 }
timer
here
A few points that need to be addressed:
setTimeout() does not return a promise, so you need to promisify and return it.
Flatten your chain by returning the promises from within each of the continuations rather than attempting to chain continuations within other continuations (i.e. then() inside of then()).
Do not wrap the continuation chain with a promise constructor, as the chain itself is already a promise, just return it directly instead. This is considered an antipattern.
Do not use globals, because it makes the initCPU() no longer re-entrant safe. Multiple calls to initCPU() before the promise returned by the first call resolves will result in unexpected behavior otherwise. Instead, use the appropriate scope to pass values along, which in this case is the function itself.
Allow errors to propagate to the caller and let the caller decide how to handle the error. Do not handle errors from within initCPU() unless you expect to use a fallback and continue to provide meaningful data to the caller.
const si = require('systeminformation');
const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });
function initCPU() {
// use local scope, not global
let cpuObj;
// return this promise chain directly
return si.cpu()
.then(data => {
cpuObj = data;
// return the promise to the chain
return delay(3000);
})
// let caller handle errors
// .catch(err => console.log(err))
// flatten your chain
.then(() => {
console.log('timer');
// return the promise to the chain
return si.cpuTemperature();
})
// flatten your chain
.then(data => {
console.log(data);
console.log('here');
// pass data to caller
return cpuObj;
});
}
function test(cpuObj) {
// received from last continuation of initCPU()
console.log(cpuObj);
}
initCPU()
.then(test)
// handle error from caller
.catch(err => {
console.log(err);
});
If you just want to query the cpu object immediately, and query cpuTemperature after 3 seconds, I'd do something like this using Promise.all():
// default to 3 seconds, allow it to be configurable
function initCPU(ms = 3000) {
return Promise.all([
si.cpu(),
delay(ms).then(() => si.cpuTemperature())
]).then(([cpu, cpuTemperature]) => ({
cpu,
cpuTemperature
}));
}
function test (obj) {
console.log(obj.cpu);
console.log(obj.cpuTemperature);
}
initCPU()
.then(test)
.catch(err => {
console.log(err);
});

Get async value from firestore

I am struggling with async operations. I am trying to simply get a value from firestore and storing it in a var.
I manage to receive the value, I can even save it in the var when I do that specifically (use the var within the get function) but I don't seem to manage the await properly when trying to save this in a flexible way:
async function getValues(collectionName, docName,) {
console.log("start")
var result;
var docRef = await db.collection(collectionName).doc(docName).get()
.then(//async// (tried this as well with async) function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return //await// (this as well with async) result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
console.log("end");
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
helpMessage = getValues('configuration','helpMessage');
Note: doc.data().text -> "text" is the name of the field where my value is stored in. Do I have to use .value here?
The result I get in the console is: info: Document data: { text: 'The correct text from the database' }
info: The correct text from the database
But using helpMessage in my code I get {}
Image from the Telegram bot where I am trying to use the helpMessage as a response to the '/help' command.
I have checked: getting value from cloud firestore,
Firebase Firestore get() async/await, get asynchronous value from firebase firestore reference and most importantly How do I return the response from an asynchronous call?. They either deal with multiple documents (using forEach), don't address the async nature of my problem or (last case), I simply fail to understand the nature of it.
Additionally, both nodejs and firestore seems to be developing rapidly and finding good, up-to-date documentation or examples is difficult. Any pointers are much appriciated.
You have things the wrong way around. It's much easier than you think it is.
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then(function (doc) {
if (doc.exists) return doc.data().text;
return Promise.reject("No such document");
}};
}
If a function returns a promise (like db.collection(...).doc(...).get()), return that promise. This is the "outer" return above.
In the promise handler (inside the .then() callback), return a value to indicate success, or a rejected promise to indicate an error. This is the "inner" return above. Instead of returning a rejected promise, you can also throw an error if you want to.
Now you have a promise-returning function. You can use it with .then() and .catch():
getValues('configuration','helpMessage')
.then(function (text) { console.log(text); })
.catch(function (err) { console.log("ERROR:" err); });
or await it inside an async function in a try/catch block, if you like that better:
async function doSomething() {
try {
let text = await getValues('configuration','helpMessage');
console.log(text);
} catch {
console.log("ERROR:" err);
}
}
If you want to use async/await with your getValues() function, you can:
async function getValues(collectionName, docName) {
let doc = await db.collection(collectionName).doc(docName).get();
if (doc.exists) return doc.data().text;
throw new Error("No such document");
}
Since getValues function returns a promise, you need to await getValues function while calling it.
Change getValues like so -
function getValues(collectionName, docName,) {
console.log("start")
var result;
return db.collection(collectionName).doc(docName).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
Then use getValues like so -
helpMessage = await getValues('configuration','helpMessage');
Explanation -
async, await are just syntactic sugar for Promises. async functions return a promise (or AsyncFunction more accurately) which needs to be resolved to use its enclosed value.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Finally managed to get it working. Thanks for the input Tomalak!
getValues(help.collectionName, help.docName)
.then((text) => {
console.log(text);
help.message = text;
})
.catch((err) => { console.log("Error: ", err); });
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then((doc) => {
if (doc.exists) {
return doc.data().text;
}
else {
return Promise.reject("No such document");
}});
}
bot.help((ctx) => ctx.reply(help.message));
Unfortunately, I can not pin-point the exact reason this worked. Some little fixes (missed comma in the console.log) and formatting definitely helped me understanding the structure though. Hope someone else finds this useful, when starting to play around with node and firebase.

Resources