Good day.
Im working on a express project and havent been ables to fully understan how async functions an promises work. This is my project structure
-controllers
--userController.js
-services
--paymentService.js
payment service is the implementation of a third-party payment tool, and it defines a method im using like this
const pay = async(data) => {
service.pay()
.then(function(response){
return response;
}).catch(function(err)){
console.log(err);
});
}
im calling this method from my userController
let payUser = async(req, res) => {
const response = await paymentService.pay(req.data);
if(response) {
res.send(response)
}
}
but response is always undefined, it doesnt wait as i thought it shoudl do because of the "await" keyword.
How does this exactly works? what am i doing wrong if i want to make it wait till the response is returned?
There's an issue in your code for the pay function:
const pay = async(data) => {
service.pay()
.then(function(response){
return response;
}).catch(function(err)){
console.log(err);
});
}
You don't return anything. You just run service.pay(). Therefore, the signature of the function pay (declared on the first line) is a function that returns a promise, but the return type is Promise<void>, not Promise<YourData>. This causes it to always return undefined, even when called with await, like in your complete example code.
I notice you have the code return response; inside the "then" callback. This may mean you intended for that data to be returned to the caller of pay (not service.pay) when the data was ready. To do this, should rewrite your pay function in one of two ways:
Replace service.pay ... with return service.pay ... so that the promise returned by service.pay is actually returned to the caller of pay.
Rewrite it so that you remove the "then" callback and replace service.pay ... with return await service.pay .... You've already put in the work to design pay as an async function, so you can now use await inside it, which often makes this kind of code more straightforward to write and read later on.
Related
Im refactoring a billing system made with Angular + NodeJs.
I need to make some API calls to my backend, first to get Customers, then Invoices and finally Students. I call the functions in ngOnInit. Then I need to manipulate the Arrays mixing the data.
If I call the functions in order, sometimes the code can't access all the info, for example:
ngOnInit(): void {
this.getCustomers();
this.getInvoices();
this.getStudents();
this.doSomeCalculations();
Sometimes customers[] (defined by this.getCustomers() is still empty when this.doSomeCalculations() starts.
I tried with setTimeout, with no improvement.
Finally what I did to make everything work was calling getInvoices() in the getCustomers() response, and getStudents() in getInvoices() response. So getInvoices() asign the response to invoices[] and then calls the next function, and so on. And doSomeCalculations() only starts when all the data has been asigned to variables.
But this solution is ugly. I know there is a better way of doing this, but I don't know how. Maybe it is related to promises or async-await?
My base code for the API calls is as follows:
getCustomers(){
this._customerService.getCustomers()
.subscribe(
{ next: (res) => {
this.customers = res;
this.getInvoices();
},
error: (err) => {
console.log(<any>err);
}
}
);
}
It is the same for getInvoices and getStudents.
How can I improve my code here?
You could try using async/await + promises.
For each method definition, you could return the data as a promise, then use async/await at the top level. I see you're using observables for the data calls, but you don't have to. Promises may be better here.
Example:
async ngOnInit(): Promise<void> {
try {
await getCustomers();
...
} ...
}
getCustomers(): Promise<Customers[]>{
return new Promise((err, resp) => {
...
resolve(resp)
})
}
I find lots of posts that almost answer the question, but nothing quite works for me.
I have an async function:
const doStuff = async (data)=>{
if(data == "a bug")
throw(new Error('Error! Bug found'));
return "not a bug";
}
I call the function on a route handler:
app.get('/doStuffRoute', async (req, res, next)=>{
const result = await doStuff("a bug")
.catch((err)=>{return next(err);});
console.log("This shouldn't execute!");
}
The error gets handled just fine by my custom error middleware, prints out the error, etc. But then I still see This shouldn't execute! being printed as well!
From all my research and reading, await X.catch(error) should be identical to try{ await X } catch {error}. And I really prefer .catch(); if at all possible. There are dozens and dozens of tutorials showing this, and even a few stackoverflow posts explicitly stating this to be the case. However I also occasionally find a post cryptically saying "don't use await with .catch()" with no other explanation or detail, so I don't know what to think.
The only hint I can find is adding return before your next(err) call should be enough to halt execution, but it doesn't seem to actually work if return next(err); is inside a .catch() block.
What is going on? Are my sources lying? Am I misunderstanding some basic fundamental concept? I appreciate any help getting me on track.
As a bonus question, why do so many people suggest a wrapper function a-la express-async-handler? Why would this have any different behavior than just calling the async function directly and handling it?
A rejected promise does not stop further Javascript execution so that's why the .catch() handler doesn't stop anything. In fact, since async functions are asynchronous, your console.log() will even execute before the .catch() handler does. As such, you need to design code flow that respects the promise state.
I would suggest this:
app.get('/doStuffRoute', async (req, res, next) => {
try {
const result = await doStuff("a bug");
console.log("doStuff() resolved");
} catch(err) {
console.log("doStuff() rejected");
next(err);
}
}
This way, you've clearly delineated the two code paths for a resolved promise and for a rejected promise and you can place code in the appropriate code path.
When you do
const result = await doStuff("a bug").catch((err)=>{return next(err);});
console.log(result) // you will see undefined
because it is trying to evaluate next method when you pass parameter err and store it in result variable, similarly if you do something like this,
const add = (err)=>{
if(err instanceof Error){ return 2}
return 3
}
const result = await doStuff("a bug").catch((err)=>{return add(err);});
console.log(result) // you will see 2
Answering your question, when you have a return statement inside a callback function only the callback function's execution ends. which means the main parent function will continue it's execution.
const result = await doStuff("a bug").catch((err)=>{return err;});
console.log(result) // now result is an Error object
if(result instanceof Error){
next(result) //if an error handler is defined call that or call default express error handler
return //stop execution of route callback
}
console.log("will NOT execute if there is an error")
but I would like to add there are better way of handling errors for async express routes, This article explains it in detail.
https://zellwk.com/blog/async-await-express/
i know there's a bunch of related questions / posts regarding this question but they don't really answer my question, my question here is simple, if I have a promise and i wrapped it in an async function and await it til it's settled like this:
async function getUserData() {
return fetch('url'); // fetch returns a promise, wrapping the user data returned
}
async main() {
const user = await getUserData();
// what is the type of the user object here?
}
I'm asking this question because I'm using TypeScript, I usually try to type cast the return value to the expected type like this:
async function getUserData() {
return fetch('url') as UserInfo;
}
say UserInfo has a name attribute but if I try write in this way then user.name is undefined:
async function main() {
const user = await getUserData();
console.log(user.name); // undefined.
}
it makes me how should I 'unwrap' a promise with a value in it?
You can't really know at compile time what it is, because this is explicitly something that happens at runtime.
So usually the result of a JSON.parse() is any. A stricter version could be the unknown type.
If you want to just assume whatever you got back is going to be the right type, without validating (because you trust the server), I think I would do that as such:
async function getUserData(): Promise<UserInfo> {
return fetch('url'); // fetch returns a promise, wrapping the user data returned
}
I could be wrong, but I think the way that Async/Await works is that it wraps the function in a native promise.
I tested out your examples in the browser console and both functions return a Promise.
Promise {<pending>}
I am not sure about what that is cast to in Typescript as I don't use it. But if you drop into the browser console you can test all of this. It functions as a pretty good REPL.
As a function of design, the getUserData() function does not need to be async as you are not awaiting anything in it.
As for unwrapping it, you can use the fetch API since you now have a fetch result:
const data = getUserData();
data.then(response => response.json())
The module-scope variable "output" refuses to be overwritten by the async function "retrieveTextWrapper", and I cannot figure out why. My objective is to output the text on StackOverFlow's homepage. retrieveTextWrapper successfully scrapes this information, but I can't seem to assign this content to the output variable. What am I doing wrong? How can I print the scraped information from the main() function?
Note: I am using electron version 3.0.4 because bypassing CORS is less of a pain on that version.
const {BrowserWindow, app} = require('electron')
output = "this should be overwritten by the retrieveTextWrapper method"
async function main(){
navigate();
win.openDevTools();
await win.webContents.once('dom-ready',retrieveTextWrapper);
console.log(output);
//prints "this should be overwritten by the retrieveTextWrapper method"
}
function navigate() {
win = new BrowserWindow({width:900,height:900});
win.loadURL(`https://stackoverflow.com/`);
}
function retrieveText(){
return `document.querySelector("*").innerText`;
}
async function retrieveTextWrapper(){
output = await win.webContents.executeJavaScript(retrieveText().replace("*", "#content"));
}
app.on('ready',main)
win.webContents.once() does not return a promise (since interfaces generally don't accept both callbacks and return a promise at the same time).
Therefore await doesn't wait for the asynchronous operation to complete. Therefore, you're looking at output before its value has been reassigned. await only does something useful when you await a promise that is connected to the asynchronous operation you're trying to wait for.
To confirm this timing issue, add a unique console.log() statement before and after the await win.webContents.once('dom-ready',retrieveTextWrapper); and inside of retrieveTextWrapper and then you can see the sequencing of these log messages.
Yep, everything changes as it should within retrieveTextWrapper function. And your explanation makes a lot of sense. However, is it possible to wait for the callback to finish (using some other syntax aside from await)? That way, I can use the updated value for other operations in the main function?
You have a couple options.
You could "promisify" win.webContents.once() so you could then use await with it.
You could put the callback inline and put the rest of your code in main inside that callback (a classic way of dealing with asynchronous operations).
Here's an example of promisifying win.webContents.once():
function waitForDomReady() {
return new Promise((resolve, reject) => {
// may want to check if document already has dom-ready and resolve immediately
win.webContents.once('dom-ready', resolve);
});
}
And, you could then use it like this:
async function main(){
navigate();
win.openDevTools();
await waitForDomReady();
await retrieveTextWrapper();
console.log(output);
}
This assumes that the code in retrieveTextWrapper that calls win.webContents.executeJavaScript() does actually return a promise when it's done. If not, you have to promisify that too.
Nothing happens to my firestore when I call the function below. I also can't see "inside helloWorld" on my GCP logs.
exports.helloWorld = functions.https.onCall((data, context) => {
console.log("inside helloWorld);
const users = admin.firestore().collection('users');
users.doc(data.userId).get().then( // --------------------Line A
(snapshot) => {
if (snapshot.exists) {
console.log("snapshot exists");
return null;
} else {
console.log("inside else");
users.doc(data.userId).set({
name: data.name
});
return null;
}
}
).catch(() => 'obligatory catch');
return; //-----------------------------------------------Line B
});
However, when I place the return on Line A, the function works as expected and a document is created in my firestore. "inside helloWorld" is shown on my GCP logs.
Why is that so?
I really appreciate any levels of clarification.
According to the documentation for callable functions (particularly the part about sending back a result):
To return data after an asynchronous operation, return a promise. The
data returned by the promise is sent back to the client. For example,
you could return sanitized text that the callable function wrote to
the Realtime Database.
Even if you don't want to send any content in the response, Callable functions still need to respond to the HTTP request that originated them, as all HTTP transactions do.
In either case, you still need to make use of the promises in all your async calls so that Cloud Functions knows when to respond to the client, after all the work is complete. Placing the return statement on "line A" is effectively returning the promise from the async work started by get(), fulfilling this requirement. Without the return statement, Cloud Functions is terminated your function because it thinks there is no more work to complete in order to send the final response.
If you're not familiar about how promises work in JavaScript, watch my video tutorials here: https://firebase.google.com/docs/functions/video-series/