Await doesn't actually wait for an async function to complete - node.js

I'm starting to use Redis for caching in my NodeJS app. Unlike all the tutorials that can be found on the internet (for example this one: https://medium.com/geekculture/fasten-your-node-js-application-with-a-powerful-caching-mechanism-using-redis-fd76b8aa482f), I don't send the res just after calling the "cache" function. My "cache" function is a middleware returning a value if found.
Here is where the function is called:
const { requestLDAP } = require('../utils/ldap')
router.get('/:mode/:type/:code', async (req, res) => {
(...)
const resultLDAP = await requestLDAP(
type, params
);
console.log('---');
console.log(resultLDAP);
console.log('---');
And here is the part of the LDAP module:
async function requestLDAP(type, params) {
const key = JSON.stringify({type,params});
// checking the cache
await clientRedis.get(key, async (err, response) => {
if (response) {
console.log("[CACHE]");
return {
ok: true,
contenu: JSON.parse(response),
};
} else {
const simulated = Array.from({ length: 5 }, () => {
let r = { type: type.slice(0, -1) };
params.forEach((k) => {
r[k] = `${cle}_${Math.floor(100*Math.random(100))}`;
});
return r;
});
console.log("[API]");
await clientRedis.setex(cle, 10, JSON.stringify(simulated));
console.log('[SAVED]');
return {
ok: true,
contenu: simulated,
};
(...)
I don't write all the rest, which is basically all the actual LDAP call.
My problem is that the script doesn't seem to wait for the "await" to complete. Here is what I have in the console:
---
undefined
---
---
undefined
---
[CACHE]
[CACHE]
What am I missing here?
If I write console.log(response) I have the correct response, but once again AFTER expected.
[EDIT]
After helpful comments, here is how I wrote the function:
let finalResponse = await clientRedis.get(cle, async (err, response) => {
if (response) {
console.log("[CACHE]");
return response;
} else {
(...)
return {
ok: true,
contenu: finalResponse,
};
but finalResponse is a boolean, it doesn't return the real response.

As they said before, requestLDAP function returns nothing, not an actual Promise that you can wait for.
You need that requestLDAP returns a Promise because you are waiting for the result of clientRedis that you don't know when will you have it.
When you have it, you have to resolve or reject the Promise mentioned. A return here is not a return for requestLDAP, but for the caller of the callback.
Try packing and returning a promise like this:
async function requestLDAP(type, params) {
return new Promise( async (resolve,reject) => { // --> Promisify the asynchronous response
const key = JSON.stringify({type,params});
// checking the cache
await clientRedis.get(key, async (err, response) => {
if(err) reject('Something ugly happened here'); // --> Give a result for the promise in the future
if (response) {
console.log("[CACHE]");
resolve( { // --> Give a result for the promise in the future
ok: true,
contenu: JSON.parse(response),
} );
} else {
const simulated = Array.from({ length: 5 }, () => {
let r = { type: type.slice(0, -1) };
params.forEach((k) => {
r[k] = `${cle}_${Math.floor(100*Math.random(100))}`;
});
return r;
});
console.log("[API]");
await clientRedis.setex(cle, 10, JSON.stringify(simulated));
console.log('[SAVED]');
resolve( { // --> Give a result for the promise in the future
ok: true,
contenu: simulated,
} );
(...)
}
});
}
};

Related

node js call a promise function inside a chain .then

Hello I am new working with asynchronous calls I hope you can help me.
I have a .then chain where I want to call in the middle of the chain a function that returns a promise but it happens to me that the chain does not wait for it to have the result of the promise function and continues without waiting
var User = require('../models/user_model');
var mapbox = require('./helper/request_api_mapbox');
findOne_mapbox: (req, res) => {
User.findById(req.params.id)
.then(user => {
...
return user.address;
})
.then(function (adress_to_mapbox)
{
// mapbox.connect_mapbox returns a promise
mapbox.connect_mapbox(adress_to_mapbox)
.then(mapbox_coordinates => {
//inside this console.log is not read it
console.log("2 then mapbox_coordinates ", mapbox_coordinates)
return Promise.resolve(body);
})
})
.then( mapbox_coordinates => {
// in this console.log mapbox_coordinates returns undefined
console.log("last promise mapbox_coordinates",
mapbox_coordinates)
// I want to return mapbox_coordinates
return res.status(200).send({
"response": "I only receive this string "
});
})
}
the promise function is:
'use strict'
const rp = require('request-promise');
const access_token_mapbox = 'bla bla bla private';
function connect_mapbox(adress_to_mapbox) {
return new Promise((resolve, reject) => {
var options = {
method: 'GET',
uri: 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + adress_to_mapbox + '.json?access_token=' + access_token_mapbox,
json: true // Automatically stringifies the body to JSON
};
rp(options)
.then(body => {
if (body.hasOwnProperty('errcode') && body.errcode != 0) {
return Promise.reject(body);
}
console.log("inside connect_mapbox function on mapbox_model 2", body)
return Promise.resolve(body);
})
.catch(err => {
debug(err);
return Promise.reject(err);
})
})
}
module.exports = { connect_mapbox };
Inside the promise function I can see in my console.log that it makes the api call properly and the response body is ok.
I am starting in the programming, I solved the problem in the following way although I don't know if it is the most correct
I solved it by converting the connect_mapbox promise function to async await and the .then chain to async await like so:
'use strict'
const rp = require('request-promise');
const access_token_mapbox = 'bla bla bla private';
async function connect_mapbox(adress_to_mapbox) {
try {
var options = {
method: 'GET',
uri: 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + adress_to_mapbox + '.json?access_token=' + access_token_mapbox,
json: true // Automatically stringifies the body to JSON
};
let response = await rp(options);
console.log("inside connect_mapbox function on mapbox_model", response)
if (response.hasOwnProperty('errcode') && body.errcode != 0) {
return Promise.reject(response);
}
return Promise.resolve(response);
}catch(error) {
return Promise.reject(error);
}
}
module.exports = { connect_mapbox };
And the .then chain like
var User = require('../models/user_model');
var mapbox = require('./helper/request_api_mapbox');
findOne_mapbox: (req, res) => {
User.findById(req.params.id)
.then(user => {
...
return user.address;
})
.then(async function (adress_to_mapbox)
{
console.log("adress_to_mapbox thit could be deleted", adress_to_mapbox)
let mapbox_response = await mapbox.connect_mapbox(adress_to_mapbox)
console.log("async mapbox_response", mapbox_response)
return res.status(200).send({
mapbox_response
});
})
...
}

NodeJS Elasticsearch search returns promise instead of values

I made a request inside my Node server to Elasticsearch. This is working perfect, except that I always get a promise returned instead of the results.
When I console log the results they look perfect, but when I return them I either get nothing or a promise.
Can someone tell me the proper way to retrieve, and handle the data from Elasticsearch?
I am using VueJS with a Node server and the official Elasticsearch package.
function getNewTest(client)
{
client.search({
index: 'myIndex',
}).then(function(resp) {
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
let tests = getNewTest(client);
console.log(tests);
# Output: Promise { <pending> }
EDIT:
As suggested I tried both codes, both didnt work. I changed my own code, now it returns an "undefined" to me.
getNewTest(client).then(function (response) {
console.log(response);
});
will return "undefined" to me. I changed my function to this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
When I would do
let test = getNewTest(client);
it returns a promise to me.
(async () => {
let tests = await getNewTest(client);
console.log(tests);
})();
You are making a db call, so the main thread becomes free and start executing the next line. The code needs to wait till the promise is resolved and then execute the next line.
Or if you dont want to use async await, you can use this code below -
async function getNewTest(client) {
client.search({
index: 'myIndex',
}).then(function (resp) {
return resp.hits.hits;
}, function (err) {
console.trace(err.message);
});
}
getNewTest(client).then(result => {
console.log(result);
});
Function getNewTest will alway return undefined because you do not explicitly return anything.
Even if you did it this way :
function getNewTest(client)
{
return client.search({
index: 'myIndex',
}).then(function(resp) {
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
It will return a promise.
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
function test () {
return generatePromise()
.then((data) => console.log('after promise resolved ', data))
.catch((err) => console.log(err))
}
console.log('before calling test function');
const result = test()
console.log('after calling test function', result instanceof Promise);
When we call a function that returns a promise (async work) the execution does not wait for the promise to resolve it continue executing other code, that why const result = test() won't have the result of the promise.
The result of the promise will only be available inside the then handler as you can see in the code snippet.
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
function test () {
return generatePromise()
}
console.log('before calling test function');
test()
.then((data) => console.log('after promise resolved ', data))
.catch((err) => console.log(err))
console.log('after calling test function');
You can achieve the required using async & await:
const generatePromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1500)
}, 500)
})
// async function always returns promise even if you did't explicitly do
async function test () {
const data = await generatePromise();
// you can access resolved data here
console.log('after promise resolved ', data);
return data;
}
// first without then
console.log('before calling test function');
// you can't access it here unless you use then
// test().then(data => console.log(data));
const result = test();
console.log('after calling test function', result instanceof Promise);
This is how asynchronous works you can't return a promise and expect to receive the results instantly you can access the results inside the then handles or use await as I did.
When you do this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
it means this:
async function getNewTest(client)
{
await client.search({
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
return undefined; // you are deliberately returning undefined
}
Remember that in javascript if you don't return anything the result of the function is undefined. I'm guessing what you intend to do is:
async function getNewTest(client)
{
return await client.search({ // NOTE THIS LINE
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
Since .search() already returns a Promise (or promise-like object) you don't need to await for it. The code above is exactly the same as:
function getNewTest(client)
{
return client.search({ // NOTE THIS RETURN
index: 'myIndex',
}).then(function(resp) {
console.log(resp.hits.hits, 'returned');
return resp.hits.hits;
}, function(err) {
console.trace(err.message);
});
}
But still, this does not allow you to do let test = getNewTest(client). Nothing will ever make this possible. It is simply impossible. To get the result of getNewTest() either call it's .then() method or await for it. In other words, either do this:
getNewTest(client).then(function(test) { /*continue logic here*/ })
or do this:
async function foo () {
let test = await getNewTest(client);
/*continue logic here*/
}
foo();
Note that this mechanism applies everywhere. It is also impossible to do this:
async function foo () {
let test = await getNewTest(client);
return test;
}
let test = foo();
You must instead do this if you want to go this route:
async function foo () {
let test = await getNewTest(client);
return test;
}
async function bar () {
let test = await foo();
/*continue logic here*/
}
There is no escape. You can never directly return an asynchronous value.

Return from async function in hapi route

Using hapi v17
i have a route
{
method: 'GET',
path: '/redirectEbay',
handler: registerController.ebayRedirect
}
that leads to a controller
ebayRedirect: function ebayRedirect(request, reply) {
ebay.xmlRequest({
serviceName: 'Trading',
opType: 'GetSessionID',
appId: EBAY_CLIENT ,
devId: EBAY_DEV ,
certId: EBAY_SECRET ,
params: {
RuName: EBAY_RUNAME
}
},
function(error, data) {
console.log(data);
console.log(error);
sessionID = data.sessionID;
//catch ???
});
return (SessionID);
}
and then of course SessionID is undefined as its generated from an async function.
Attemp with async / await:
ebayRedirect: async function ebayRedirect(request, reply) {
const session = await ebay.xmlRequest({
...
params: {
RuName: EBAY_RUNAME
}
}, function(error, data) {
sessionID = data.sessionID;
return sessionID;
});
return (session);
}
It gives another error, looklike the whole handler is considered malformed because not returning someting ?
the async call is correct and returning the session
Debug: internal, implementation, error
Error: ebayRedirect method did not return a value, a promise, or throw an error
Another try with a different taste, still not resolving, like the await does not wait function to resolve as console.log is triggered immediately
At least got rid of the Error 500...
also tried a variation :
ebayS = async function() {
console.log ( ebay() );
gives
Promise { undefined }
The ebay.xmlRequest function uses a callback instead of a promise, so you have to wrap it in a promise:
ebayRedirect: function ebayRedirect(request, reply) {
return new Promise((resolve, reject) => ebay.xmlRequest({
params: {
RuName: EBAY_RUNAME
}
},
function(error, data) {
if (error) {
reject(error);
} else {
resolve(data.sessionID);
}
}
));
}

Javascript async/await response not ready

I am having difficulties getting and moving a response from the following functions in a chain.
The values exist further up but however I try to pass them down, the output always ends up empty.
I'm new to async/await in nodejs, but assumed they didn't need to be resolved like or with a promise.
let output = {
keywords: [],
steps: [],
twitter: []
}
async function requestGoogle (output) {
const GoogleResponse = await (
new Promise((resolve, reject) => {
const loc = utilities.checkOrigin()
googleMaps.directions({
origin: loc.origin,
destination: loc.destination
}, (error, response) => {
if (error) resolve(error)
resolve({
response
})
})
})
)
return GoogleResponse
}
async function requestTwitter (output) {
try {
const loc = utilities.checkOrigin()
const twitterParams = {
count: details.twitter.count,
geocode: loc.lat + loc.long + details.radius,
lang: details.lang,
result_type: 'recent'
}
await output.keywords.map(keyword => {
TwitterClient.get('search/tweets', {q: 'query'}, (error, tweets, response) => {
if (error) console.log(error)
tweets.statuses.map(tweet => {
output['twitter'].push({
text: tweet.text
})
})
})
})
return output
} catch (error) {
console.error('++ requestTwitter flow', error)
}
}
flow.commuteCheck = () => {
return requestGoogle(output)
.then(requestTwitter(output))
.then(console.log(output))
}
The response is unmodified and empty:
{ keywords: [], steps: [], twitter: [] }
full file on Github
.then(requestTwitter(output)) calls requestTwitter(output) and passes its return value (a promise) into then. But then expects a callback, not a promise. Similarly with .then(console.log(output)).
You probably meant to wrap those in functions:
flow.commuteCheck = () => {
return requestGoogle(output)
.then(() => requestTwitter(output))
// ---^^^^^^
.then(() => console.log(output))
// ---^^^^^^
}
FWIW, if requestGoogle and requestTwitter don't have to run in series, you can overlap them so the requests run concurrently:
flow.commuteCheck = () => {
return Promise.all([
requestGoogle(output),
requestTwitter(output)
]).then(() => console.log(output));
}
It's also probably worth noting that in both cases, the promise resolves with no useful value for the caller. You might have it resolve with output. To provent crosstalk between requests, you could also make output not be a module global that gets reused, but instead an object you create for each request:
flow.commuteCheck = () => {
const output = output = {
keywords: [],
steps: [],
twitter: []
};
return Promise.all([
requestGoogle(output),
requestTwitter(output)
]).then(() => {
console.log(output);
return output;
});
}

Using async await properly in node js

To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify

Resources