Strange Promise.all behaviour - node.js

In one file called SearchBuilder.js i have this function ( wrote in this way as pure test )
self.validateInputs = async params => {
const validateDates = async () => {
const pick_up_date = moment(`${params.pick_up_date} ${params.pick_up_time}`),
drop_off_date = moment(`${params.drop_off_date} ${params.drop_off_date}`);
return true;
};
const validatePickUpId = async () => {
return true;
};
const validateDropOffId = async () => {
throw { 'code': 'NOT_FOUND'};
};
return Promise.all([
validateDates,
validatePickUpId,
validateDropOffId,
]);
};
In another file called searchEngine.js i have this:
self.run = async (type, search_inputs, account) => {
const searchBuilder = self.getSearchBuilder(type);
try {
const isValid = await searchBuilder.validateInputs(search_inputs);
console.log(isValid);
} catch (e) {
console.log('error input validation');
}
return search;
};
I erroneously thought that if any of the promises in Promise.all would fail, so my code will enter the catch block. Instead, even if the promise fails code inside the catch is never executed and inside isValid i get this.
[ [AsyncFunction: validateDates],
[AsyncFunction: validatePickUpId],
[AsyncFunction: validateDropOffId] ]

Related

async/await troubles in a recursive Redis function

Ima rookie using async/await but must now to use Redis-om. NN_walkd walks through a Redis database looking for loop-chains and does this by recursion. So the 2 questions I have is:
Am I calling the inner recursive NN_walkd calls correctly via async/await?
At runtime, the compSearchM proc is called first and seems to work (it gets 5 entries so it has to call NN_walkd 5 times). A NN_walkd is then recursively called, and then when it loops the 1st time it then calls compSearchK where the problems are. It seems to sit on the first Redis call in compSearchK (.search). Yet the code for compSearchK and compSearchM look basically identical.
main call
NN_walk = async function(req, db, cnode, pnode, chain, cb) {
var vegas, sneaker;
req.session.walk = [];
await NN_walkd(req, cnode, pnode, [], 1);
req.session.walk = null;
console.log('~~~~~~~~~~~~ Out of Walk ~~~~~~~~~~~~~~~');
cb();
};
redis.mjs
export class RedisDB {
constructor() {
...
this._companyRepo = ...
}
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
compSearchM(mkey) {
var tArr=[];
return new Promise(async (resolve) => {
const smkey = await this._companyRepo.search()
.where('MASTERKEY').equals(mkey)
.and('TBLNUM').equals(10)
.return.all();
if (smkey.length) {
for (var spot in smkey) {
const ttrr = await this._companyRepo.fetch(smkey[spot].entityId);
tArr.push(ttrr.toJSON());
}
resolve(tArr);
} else {
resolve(null);
}
});
}
walk.js
NN_walkd = async function(req, cnode, pnode, chain, lvl) {
...
if (cnode[1]) {
const sObj = await req.app.get('redis').compSearchK(cnode[1]);
if (sObj) {
int1 = (sObj.TBLNUM==1) ? null : sObj.CLIENTKEY;
(async () => await NN_walkd(req, [sObj.COMPANYKEY,int1], cnode, Array.from(chain), tlvl))()
}
} else {
const sArr = await req.app.get('redis').compSearchM(cnode[0]);
if (sArr.length) {
for (sneaker in sArr) {
(async () => await NN_walkd(req, [sArr[sneaker].COMPANYKEY,sArr[sneaker].CLIENTKEY], cnode, Array.from(chain), tlvl))()
}
} else {
console.log('no more links on this chain: ',cnode);
}
}
}
"doesn't matter if i have async or not here"
compSearchK(ckey) { // doesn't matter if I have a async or not here
return new Promise(async (resolve) => {
const sckey = await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
if (sckey.length) {
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
resolve(ttrr.toJSON());
} else
resolve(null);
});
}
Of course it doesn't matter, because you're not using await inside of compSearchK!
You are using the explicit promise contructor anti-pattern. You should avoid it as it demonstrates lack of understanding. Here is compSearchK rewritten without the anti-pattern -
async compSearchK(ckey) {
// await key
const sckey =
await this._companyRepo.search()
.where('COMPANYKEY').equals(ckey)
.return.all();
// return null if key is not found
if (sckey.length == 0) return null;
// otherwise get ttrr
const ttrr = await this._companyRepo.fetch(sckey[0].entityId);
// return ttrr as json
return ttrr.toJSON();
}

How to build an eventual return value though multiple promises

How do I chain some promises (NodeJS) together to continually enhance and object with new data and return the final object at the end of the chain. ie:
return getOrder().then((order) => {
return StatusMachine.getStatus(order.orderId);
}.then((status) => {
// get status response
// how do I access the "Order" in the previous then
// so I can do something like
order.status = status;
return order;
}).then((order) => {
return Shipping.getTrackingNumber(order.orderId);
}).then((tracking) => {
order.trackingNumber = tracking;
return order
});
Goal here is to load an order, then query its status and add it to the order, then query its tracking number and add it to the order. Currently, I'm setting Order as a global variable so that I can access it from every then() function and then in the final then(), I'm simply returning the global Order variable. However, that doesn't feel right.
To get order & status you need to wrap it with async function like this.
getOrder()
.then(async (order) => { // <--
const status = await StatusMachine.getStatus(order.orderId) // <-- await for status
return { order, status }
})
.then(({ status, order }) => {
order.status = status
return Promise.resolve(order) // if you are going to chain `.then` again then you have to return promise (converting this to async function will also works)
})
//.....
But I high recommend going full async, its much more readable and error handling becomes super easy
const getOrderWithMetadata = async () => {
const order = await getOrder()
const status = await StatusMachine.getStatus(order.orderId)
order.status = status
const tracking = await Shipping.getTrackingNumber(order.orderId)
order.trackingNumber = tracking
return order
}
or with Promise.all
const getOrderWithMetadataBetter = async () => {
const order = await getOrder()
const [status, tracking] = await Promsie.all([ // waits for both to complete, throws error if anyone of them fails.
StatusMachine.getStatus(order.orderId),
Shipping.getTrackingNumber(order.orderId),
])
order.status = status
order.trackingNumber = tracking
return order
}
Edit: It depends on how you want to handle error.
case 1: Ignore errors
const getOrderWithMetadata = async () => {
try {
// ...
} catch (err) {
// ...
}
}
case 2: return data and error (like go lang does.)
const getOrderWithMetadata = async () => {
try {
// ...
return [value, null] // [result, error]
} catch (err) {
// ...
return [null, err] // [result, error]
}
}
const [data, err] = await getOrderWithMetadata()
case 3: handle error outside using try/catch
const getOrderWithMetadata = async () => {
//...
}
try {
// ...
await getOrderWithMetadata()
//...
} catch (err) {
// ...
}
case 4: handle error outside using promise
const getOrderWithMetadata = async () => {
//...
}
getOrderWithMetadata().then(() => /* ... */).catch(() => /* ... */)

Cannot get the result of an async function in controller

This is my controller:
const rssService = require('../services/rss.service');
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res) {
const p = new Promise((resolve, reject) => {
const t = parser();
if (t === undefined) {
resolve(t);
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject('something bad happened');
}
});
p.then((result) => res.send(result)).catch((message) => console.log(`ERROR ${message}`));
};
module.exports = {
parse,
};
in the function : parser() above, I am trying to call my rss.service.js file which I have the logic. This file is a rss parser which tries to parse the feed and do some calculations (which needs promises and async) and then return the json object.
Here is how my rss.service look :
const rssParser = async function parseRssFeed(url) {
const parser = new Parser();
const appRoot = process.env.PWD;
const downloadDir = `${appRoot}/downloads/`;
if (!fs.existsSync(downloadDir)) {
fs.mkdirSync(downloadDir);
}
try {
const feed = await parser.parseURL('someurl');
const processedFeedItems = await Promise.all(feed.items.map(async (currentItem) => {
const {
fileUrl,
downloadPath,
} = await downloadFile(currentItem);
const hashValue = calculateHash(downloadPath)
return {
title: currentItem.title,
hash: hashValue,
url: mp3FileUrl,
};
}));
return (JSON.stringify(processedFeedItems));
} catch (error) {
console.error(error);
return 'error';
}
};
when I debug my code I can verify that Json object has been created with correct data, but the result does not return to the callee(controller).
I'll go in a little deeper since you mentioned you're new:
const rssService = require('../services/rss.service');
// This is an async function (always returns a promise)
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res, next) {
// In await/async, you should use try/catch/throw instead of .then and .catch
// It does the same thing, but it's the preferred syntax and is "easier" to read IMO
// Out in "the community" people will complain if you mix await/async with promises like that
try {
// Adding await to ensure left-assign works.
// This is necessary because parser is an async function (so returns a promise)
const result = await parser();
// here, since you used `await` you get the value instead of the promise
if (result === undefined) throw new Error('something bad happened')
return res.send(result)
} catch (error) {
console.log(`ERROR ${error.message}`;
// Do you need to do anything else with this error? I assume something like:
return next(error);
}
};
module.exports = {
parse,
};
In a fast look, It seems you have forgot to wait for resolve the parser promise.
...
const p = new Promise(async(resolve, reject) => {
const t = await parser();
...

cant return the chained promises result

const request = require('request-promise')
required this module and use it in this way the data and subData is options that i defined later...
const foo= (data, subData) => {
return request(data)
.then(result => {
console.log('result:',result)
return request(subData)
})
}
the problem is the request(data) result is not return but the request(subData) result is return
Q.allSettled([
foo(),
fo(),
f(),
.
.
.
])
and with q module create an array of promises, but i still cant get my expected return result
You can use any of the following methods to chain your promises and return both responses into an array
const foo = (data, subData) => {
let result;
return request(data)
.then(res => {
result = res;
return request(subData)
}).then(res => {
return [result, res]
});
}
//OR
const foo2 = (data, subData) => {
return request(data)
.then(res1 => {
return request(subData).then(res2 => {
return [res1, res2]
})
});
}
//OR
const foo3 = async (data, subData) => {
let res1 = await request(data);
let res2 = await request(subData);
return [res1, re2];
}
I would suggest the following approach instead of Promise Chaining or Q:
const fetchData = (data, subData) => {
const result = await request(data);
const resultSub = await request(subData);
console.log(result, resultSub);
};

Node JS Promise then block executed before promise resolution

Simple code path, that is better explained with bullet points
API call
Call to DB
Result is a list of objects
Inside the then block make a DB call for each object to hydrate children
Inside another then block res.send(hydrated object)
Problem
Step 5 happens before step 4 completes (Code below)
//api endpoint
router.post('/get-data', getObjects);
export const getObjects: express.RequestHandler = (req, res) => {
queryContainer(containerId, querySpec)
.then((result) => {
return getChildren(result, req.body.criteria);
})
.then((result) => {
res.send(result);
});
}
export async function queryContainer(containerId, querySpec) {
const { result: results } = await client.database(databaseId).container(containerId).items.query(querySpec, {enableCrossPartitionQuery: true}).toArray()
.catch( (error) => {
console.log("Error! ", error);
});
return results;
}
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
return result;
});
}
return result;
}
export const dbCallToGetChildren = (async function (username) {
const querySpec = {
query: "SELECT * FROM root r WHERE r.userName=#userName",
parameters: [
{name: "#userName", value: username}
]
};
queryContainer(containerId, querySpec)
.then((results) => {
return results;
})
.catch((error) => {
console.log("Error " + error);
return Promise.resolve;
});
});
I have a few comments about your code:
you are doing mutation which is a bad practice, getChildren function accepts result type any and then it makes some changes to it like
result.children = []
Avoid using any and try to define a type
in your code because you are doing mutation so you do not even need to return back the result because the original object is already changed but as I mentioned earlier you should avoid mutation.
The main problem that step 5 executes before step 4 is that getChildren won't return back promise, I made some changes in your code to accommodate promise.
function getChildren(result: any, criteria: any): Promise<any> {
return new Promise((resolve, reject) => {
if (criteria) {
result.children = []
const actions = result
.map(item => item.element)
.map(dbCallToGetChildren)
Promise.all(actions).then(children => { // returns a promise
result.children.push(...children)
return resolve("successfully is processed!")
})
}
reject("invalid criteria!")
})
}
Step 5 happens before step 4 (getChildren function) completes because getChildren isn't returning a Promise. Changing it to the following might fix the problem:
function getChildren(result: any, criteria: any) {
return new Promise(resolve => {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
resolve(result);
});
} else {
resolve(result);
}
});
}
Inside results.then(children => { ... } there is now resolve(result); to ensure the return getChildren(result, req.body.criteria); statement within the queryContainer call doesn't complete until the Promise resolves.
In order for step 4 to fully complete before step 5 is executed, we need to promisify every code-path that goes through getChildren.
That means we should change this:
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
return result;
});
}
return result;
}
Into the following (pay attention to the code comments):
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
return results.then(children => { // returns a promise
result.children.push(...children)
return result;
});
}
return Promise.resolve(result); // returns a promise
}
It's important to return otherwise the code in this line is being executed in async manner (which doesn't guarantee that step 4 will complete before step 5 begins).

Resources