I was working on a project and I decided to convert everything to async/await. After learning how it works, I noticed that I could use the following without util.promisify().
await transporter.sendMail(message);
That happens because if we don't set the callback argument, the method returns a Promise. https://nodemailer.com/usage/
When I run it, it takes ~2000ms for the API to respond according to Postman but when I turn it into a Promise (with the help of util.promisify), it takes ~200ms to get a response, why is that?
await util.promisify(cb => transporter.sendMail(message, cb));
Am I doing something wrong or it's just promisify more optimized than the promise return?
That's because it's lying to you. You'll notice that your email isn't getting sent (so no waiting for emails which means less time).
util.promisify() does not return a Promise. It returns a function that returns a Promise:
const resultOfSendMail = transporter.sendMail(message); // notice, no await
console.log(resultOfSendMail instanceof Promise); // true
// however
const resultOfPromisify = util.promisify(cb => transporter.sendMail(message, cb));
// still no await
console.log(resultOfPromisify instanceof Promise); // false
console.log(resultOfPromisify instanceof Function); // true
The way you use Promisify is that you pass a function in, and you get a function out. Like so:
const sendMailAsync = util.promisify((msg, cb) => transporter.sendMail(msg, cb));
// sendMailAsync is a *function* that takes only message, and returns a Promise!
await sendMailAsync(message); // This returns a Promise! We can use await
You'll find that now, it takes approximately the same time to respond in both. And in both times, the email will actually be sent.
Related
I am using Firebase and Nodejs in order to calculate some values. There are 40+ JSON objects will be there in the variable 'data'. 'getOHLCofStock' function expects a value and it will return a promise. But what happens here is, that the console.log function gets executed without waiting for the promise to resolve. Therefore, I am getting 'undefined' in the console.
Please help me to make this function wait and console the real value.
const path = db.ref(now);
path.on('value', async(snapshot) => {
for (const property in data) {
await zerodha.getOHLCofStock(property).then(async ohlc => {
console.log(ohlc);
//ohlc is 'undefined', but in fact, its not undefined. The 'await'
is not working here.
I want 'getOHLCofStock' should wait until the value gets resolved and
then only the console.log should execute.
})
}
})
I am using Dialogflow to build an Action for Google Assistant. Everything works, except the Fulfillment of my intent.
I am using the Inline Editor (Powered by Cloud Functions for Firebase) to fulfill the intent. My function in itself runs - since I can send text to the assistant from the function.
But for some reason, code execution never enters the the function that fetches data from my Collection on Firebase Firestore - although it does execute commands before and after.
Here is the code in my index.js.
'use strict';
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
let db = admin.firestore();
const {dialogflow} = require('actions-on-google');
const app = dialogflow({debug: true});
app.intent('INTENT', (conv, {ENTITY}) => {
conv.add("Hello."); //THIS IS DISPLAYED
db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
conv.add("Hey!"); //THIS IS NOT DISPLAYED
snapshot.forEach(doc => {
conv.add("Hi?"); //NOR IS THIS
});
conv.add("Hmm..."); //NEITHER THIS
}).catch(error => {
conv.add('Error!'); //NOT EVEN THIS
});
conv.add("Bye."); //THIS IS DISPLAYED TOO
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Clearly, this shows that execution never really entered the db... block, and hence the function didn't even throw any error.
Here are the logs from Firebase.
Function execution started
Billing account not configured...
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
Request {...}
Headers {...}
Conversation {...}
Response {...}
Function execution took 1681 ms, finished with status code: 200
I know that the firestore function fetches data asynchronously, but there
seems no way I could execute anything even inside its .then(...) block.
I also tried returning a Promise from the .then(...) block, and using a second .then(...) with it - which again didn't work.
var fetch = db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
conv.add("Hey!"); //NOT DISPLAYED
var responseArr = [];
snapshot.forEach(doc => {
conv.add("Hi?"); //NOT DISPLAYED
responseArr.push(doc);
});
conv.add("Hmm..."); //NOT DISPLAYED
return Promise.resolve(responseArr);
}).then(fetch => {
conv.add("Here?"); //NOT DISPLAYED
}).catch(error => {
conv.add('Error!'); //NOT DISPLAYED
});
Finally, I also tried putting the firestore function in a separate function, like this.
function getData(){
return db.collection("COLLECTION").orderBy("FIELD", "desc").get().then(snapshot => {
snapshot.forEach(doc => {
...
});
return data; //Manipulated from above. 'data' can be a string.
}).catch(error => {
return error;
});
}
app.intent('INTENT', (conv, {ENTITY}) => {
conv.add("Hello."); //THIS IS DISPLAYED
conv.add(getData()); //THIS IS NOT DISPLAYED
conv.add("Bye."); //THIS IS DISPLAYED
});
The problem is that you're doing an asynchronous operation (the call to get()), but you're not returning a Promise from the Intent Handler itself. The library requires you to return a Promise so it knows that there is an async operation taking place.
Returning a Promise from inside the then() portion isn't enough - that doesn't return a value from the handler, it just returns a value that is passed to the next then() function or (if it was the last one) as the return value of the entire Promise chain.
In your original code, this can be done just by returning the get().then().catch() chain. Something like this as your first line:
return db.collection("COLLECTION").orderBy("FIELD", "desc").get() // etc etc
In your second example, the fetch in your then() block is not the fetch that you think it is, and only confuses matters. Structured that way, you need to return the fetch from the let assignment.
Your third example is more complicated. The line
conv.add(getData());
doesn't even seem like it would work, on the surface, because it is returning a Promise, but you can't add a promise to the conv object. You would need to rewrite that part as
return getData()
.then( data => conv.add( data ) );
But that doesn't address how the "Bye" line would work. If you actually wanted "Bye" after the data, you would have to include it as part of the then() block.
In short, when dealing with async data, you need to
Make sure you understand how Promises work and make sure all async work is done using Promises.
Add all your data inside the then() portion of a Promise
Return a Promise correctly
it's the first time for me using async/await. I've got problems to use it in the context of a database request inside a dialogflow intent. How can I fix my code?
What happens?
When I try to run use my backend - this is what I get: "Webhook call failed. Error: Request timeout."
What do I suspect?
My helper function getTextResponse() waits for a return value of airtable, but never get's one.
What do I want to do?
"GetDatabaseField-Intent" gets triggered
Inside it sends a request to my airtable database via getTextResponse()
Because I use"await" the function will wait for the result before continuing
getTextResponse() will return the "returnData"; so the var result will be filled with "returnData"
getTextResponse() has finished; so the response will be created with it's return value
'use strict';
const {
dialogflow
} = require('actions-on-google');
const functions = require('firebase-functions');
const app = dialogflow({debug: true});
const Airtable = require('airtable');
const base = new Airtable({apiKey: 'MyKey'}).base('MyBaseID');
///////////////////////////////
/// Helper function - reading Airtable fields.
const getTextResponse = (mySheet, myRecord) => {
return new Promise((resolve, reject) => {
// Function for airtable
base(mySheet).find(myRecord, (err, returnData) => {
if (err) {
console.error(err);
return;
}
return returnData;
});
}
)};
// Handle the Dialogflow intent.
app.intent('GetDatabaseField-Intent', async (conv) => {
const sheetTrans = "NameOfSheet";
const recordFirst = "ID_OF_RECORD";
var result = await getTextResponse(sheetTrans, recordFirst, (callback) => {
// parse the record => here in the callback
myResponse = callback.fields.en;
});
conv.ask(myResponse);
});
// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
As #Kolban pointed out, you are not accepting or rejecting the Promise you create in getTextResponse().
It also looks like the var result = await getTextResponse(...) call is incorrect. You have defined getTextResponse() to accept two parameters, but you are passing it three (the first two, plus an anonymous arrow function). But this extra function is never used/referenced.
I would generally avoid mixing explicit promises with async/await and definitely avoid mixing async/await with passing callbacks.
I don't know the details of the API you are using, but if the API already supports promises, then you should be able to do something like this:
const getTextResponse = async (mySheet, myRecord) => {
try {
return await base(mySheet).find(myRecord)
}
catch(err) {
console.error(err);
return;
}
)};
...
app.intent('GetDatabaseField-Intent', async (conv) => {
const sheetTrans = "NameOfSheet";
const recordFirst = "ID_OF_RECORD";
var result = await getTextResponse(sheetTrans, recordFirst)
myResponse = result.fields.en;
conv.ask(myResponse);
});
...
Almost all promised based libraries or APIs can be used with async/await, as they simply use Promises under the hood. Everything after the await becomes a callback that is called when the awaitted method resolves successfully. Any unsuccessful resolution throws a PromiseRejected error, which you handle by use of a try/catch block.
Looking at the code, it appears that you may have a misunderstanding of JavaScript Promises. When you create a Promise, you are passed two functions called resolve and reject. Within the body of your promise code (i.e. the code that will complete sometime in the future). You must invoke either resolve(returnData) or reject(returnData). If you don't invoke either, your Promise will never be fulfilled. Looking at your logic, you appear to be performing simple returns without invoking resolve or reject.
Let me ask you to Google again on JavaScript Promises and study them again with respect to the previous comments just made and see if that clears up the puzzle.
exports.handler = async (event, context, callback) => {
try {
const { headers, body } = event;
//This is where I forgot the "await" keyword
const input = ValidateInput(body); //Returns Promise
callback(null, true);
}catch(err){
console.log(err);
callback(null, false);
}
}
When calling a function that returns a promise and forgetting to create an await expression promise function call, and that function rejects the promise, Lambda logs this error in cloudwatch
(node:1) UnhandledPromiseRejectionWarning: #<Object>
The fix is simple, don't forget the await expression
const input = await ValidateInput(body); //Return Promise
The fix is simple, don't forget the await expression
const input = await ValidateInput(body); //Return Promise
As already mentioned, the solution is to ensure you await the promise:
const input = await ValidateInput(body);
But I thought I'd add a little context around why this occurs.
Since Promises can be stored in variables and chained at any point, there's no way for the library to know whether or not a Promise chain will have a .catch associated with it some time in the future. Many libraries therefore have a default behaviour of writing to the console if the rejected Promise has not been handled within a number of passes of the event loop; which is why you see this in the log.
You should generally take this warning as implying that you haven't awaited something you should have. As in reality, it's rare that you'd see it on purpose.
The AWS doc explicitly says you don't use callback with an async function.
The third argument, callback, is a function that you can call in
non-async functions to send a response. The callback function takes
two arguments: an Error and a response. The response object must be
compatible with JSON.stringify.
For async functions, you return a response, error, or promise to the
runtime instead of using callback.
So, you may want to fix that in your lambda function.
See here : https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback
let request = require('request-promise')
function get(url) {
let _opt = {}
let response = (async () => {
try {
var ret = await request(url, _opt);
return ret;
} catch (e) {
console.log(e)
}
})();
return response
}
console.log(get('http://www.httpbin.org/ip'))
gives:
Promise { <pending> }
Why doesn't it wait for my response?
Why doesn't it wait for my response?
That's simple, because you are returning a promise. Node js is single thread and is executed in a non blocking way.
That means that return response in your get function is executed before the resolution of response variable.
Try as follow:
let request = require('request-promise')
function get(url) {
let _opt = {}
return request(url, _opt);
}
async function some () {
console.log(await get('http://www.httpbin.org/ip'));
}
some();
This example is also returning a promise, but in this case we are awaiting for the promise resolution.
Hope this help.
Async functions are non-blocking and will immediately return a promise rather than waiting for a result. This for example, allows for multiple async functions to run concurrently rather than each running sequentially.
To wait for a result, you can use the await keyword to block until the promise is resolved. The await command will return the result of the promise. For example, to log the result of the get function, you can change the last line to:
console.log(await get('http://www.httpbin.org/ip'))
UPDATE:
Let me give this one more try (sorry if I'm beating a dead horse here).
Since your response variable is an async function, it's return type is a promise by the definition of an async function. Even though the ret variable is await'ed, that just means that it will block while writing to the ret variable. The response async function needs to be run to completion in order for the await to complete. An await here would be useful if you wanted to wait for the return value and then add post-processing, but in this example specifically, your try block could simply be:
try {
return request(url, _opt)
}
Since request already returns a promise, the extra await is causing a trivial amount of overhead since it implicitly creates an extra promise.
Since response is a promise, and you are returning response, the function get is also returning a promise. You are then trying to log this promise (which obviously doesn't work). This is why you must await or .then the get function to get the result.
Source: https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8 under "Pitfall 1: not awaiting"