Node JS multliple promises chaining - javascript

I have node JS api server and I'm having issues with correct chaining of the Promises:
app.post(
"/api/tasks",
async function (_req, res) {
const newArray = [{ MyTasks: [] }];
const getOne = async (owner, taskID) => {
return await getOneDocument(owner, taskID).then((result) => {
console.log("get one doc", result);
return result;
});
};
// first promise
let toApproveTasks = await getToApproveTasks(_req.body.userID);
console.log("1", toApproveTasks);
// loop trough the result of 1st promise and run async function for each
const arrayToDoc = async (array) => {
array.TasksToApprove.forEach(async (element) => {
let objToPush = await getOne(element.Owner, element.TaskID);
console.log("1.5", objToPush);
newArray.MyTasks.push(objToPush);
});
};
// second promise
await arrayToDoc(toApproveTasks);
console.log("2", newArray);
// third promise
let finalResult = await parseCosmosOutput(newArray);
console.log("3", finalResult);
res.status(200).send(finalResult);
}
);
What I get in console is :
1 [Object] - all good
Emppty Array
Empty Array
get one doc {object} - all good
1.5 {object} - all good
How would I make sure when I loop over result of 1st promise my code awaits async function and pushes to newArray results ?

Use For..of instead of forEach inside arrayToDoc function
E.g
const arrayToDoc = async (array) => {
for(let element of array.TasksToApprove){
let objToPush = await getOne(element.Owner, element.TaskID);
console.log("1.5", objToPush);
newArray.MyTasks.push(objToPush);
}
};

Related

using async await with reduce

i have following
const imageField = ["logoUrl", "fullLogoUrl"]
const onCreate = async (submitData: any) => {
const uploadImageField = await imageField.reduce(
async function (acc: any, cur: string) {
await acc;
const url = await uploadImage(submitData.general[cur][0]);
acc[cur] = url;
return acc;
},
{}
);
console.log(uploadImageField);
}
this is my console.log
{
logoUrl: "https://........"
}
only logoUrl field is show, fullLogoUrl is missing
The problem is that acc on the second iteration is a promise object - that's why you await it. However, you still assign the [cur] property on that promise object, not on the promise result, and the implicit promise chaining of the async function as well as the explicit awaits will just ignore properties on the promise object. You could fix this by doing acc = await acc;, but really I recommend not to use reduce with async/await at all. A normal loop is much simpler and has no pitfalls.
const imageField = ["logoUrl", "fullLogoUrl"]
const onCreate = async (submitData: any) => {
const uploadImageField = {};
for (const cur of imageField) {
const url = await uploadImage(submitData.general[cur][0]);
acc[cur] = url;
}
console.log(uploadImageField);
}

How to run a few API calls in a loop and then use callback function

I'm trying to write a function but can't seem to figure out how to run a loop of API calls on an array of strings, and only after all the API calls are completed to call the callback function. No matter what I try either the function fails or the callback function is called too soon.
Here is my code:
exports.handler = function (context, event, callback) {
const client = context.getTwilioClient();
const sidArr = [];
const cc = event.ccSid;
const exp = event.expSid;
const cvc = event.cvcSid;
sidArr.push(cc, exp, cvc);
const resArr = [];
for (var i = 0; i < sidArr.length; i++) {
client
.messages(sidArr[i])
.update({ body: '' })
.then((message) => {
if (resArray.length == sidArr.length) {
callback(null, resArray);
} else {
resArray.push(message.sid);
}
});
}
};
If you can't use async and await, then you can just use Promise.all() and run them in parallel like this:
Run them in parallel:
let promises = [];
for (var i = 0; i < sidArr.length; i++) {
promises.push(
client
.messages(sidArr[i])
.update({ body: '' })
.then((message) => message.sid)
);
}
Promise.all(promises)
.then((results) => callback(null, results))
.catch((err) => callback(err));
Of course, it's a bit of an anti-pattern to be turning a promise into a plain callback like this. You really ought to just change the interface and return the promise back to the caller so the caller can just use .then() and .catch() on the promise directly.
Run them sequentially without async and await:
Or, if you want to run them sequentially (not in parallel) without using async and await, then you can do it the old-fashioned way for an asynchronous iteration (what we did before async/await):
function run(sidArr, callback) {
let index = 0;
let results = [];
function next() {
// check if we're done and call callback
if (index >= sidArr.length) {
// ensure callback is always called asynchronously
// even if sidArr is empty
setImmediate(() => callback(null, results));
return;
}
// still more to process
client
.messages(sidArr[index++])
.update({ body: '' })
.then((message) => {
// save result and do next iteration of loop
results.push(message.sid);
next();
})
.catch(callback);
}
next();
}
run(sidArr, putYourCompletionCallbackHere);
Run them sequentially with async and await:
For completeness, here's how you would run them sequentially when you can use async and await:
async function run(sidArr, callback) {
try {
let results = [];
for (var i = 0; i < sidArr.length; i++) {
let data = await client.messages(sidArr[i]).update({ body: '' });
results.push(data.sid);
}
callback(null, results);
} catch (e) {
callback(e);
}
}
run(sidArr, putYourCompletionCallbackHere);
The update method returns a Promise. Loop over the sidArr array and call the messages and update method for each item in the array. Store the returned Promise in a new array.
You can wait for all promises in an array to finish with the static Promise.all function. The result will be an array of all resolved promises.
Then loop over each result once more to extract the sid property.
exports.handler = function (context, event, callback) {
const client = context.getTwilioClient();
const sidArr = [];
const cc = event.ccSid;
const exp = event.expSid;
const cvc = event.cvcSid;
sidArr.push(cc, exp, cvc);
const requests = sidArr.map(message =>
client
.messages(message)
.update({ body: "" })
.catch(error => console.log(error))
);
Promise.all(requests)
.then(messages => messages.map(({ sid }) => sid))
.then(sids => callback(sids))
};

Trying to pass array and use foreach to send back multiple data

I had getProductInfo orgianlly, as two parameters, where it would be (res, sku). but now I want to pass a set object with sku numbers and for-each res.send the data
const activeProductBank = new Set([6401728, 6430161, 6359222, 6368084]);
getProductInfo = (res) => {
activeProductBank.forEach((SKU) => {
bby.products(SKU, { show:'sku,name' })
.then(function(data) {
res.send(data);
});
})
};
also tried this
getProductInfo = (res) => {
const allProductInfo = '';
activeProductBank.forEach((SKU) => {
bby.products(SKU, { show:'sku,name'})
.then(function(data) {
allProductInfo.concat(data);
});
})
res.send(allProductInfo);
};
The error I get "app listening at http://localhost:3000
(node:25556) UnhandledPromiseRejectionWarning: Error: Exceeded max retries"
You can use a combination of ASYNC / AWAIT and Promise.all to populate the allProductInfo as expected.
The caveat with ASYNC / AWAIT is that you can only use ASYNC function inside an ASYNC function. More about it here https://javascript.info/async-await
activeProductBank.map will iterate over all your activeProductBank and returns an array of Promises which are then passed over to the Promise.all which then resolves after all the promises in the list are reolved.
Promise.all
getProductInfo = async (res) => {
const allProductInfo = Promise.all(
activeProductBank.map(SKU => bby.products(SKU, { show:'sku,name'}))
)
res.send(allProductInfo);
};
Another approach is to use for..of loop and pushing the response of each productInfo one by one using the Await call like below
getProductInfo = async (res) => {
let allProductInfo = [];
for(let sku of allProductInfo) {
const productInfo = await bby.products(sku, { show:'sku,name'});
allProductInfo.push(productInfo);
}
res.send(allProductInfo);
};

Array reduce function with async await

I'm trying to skip one object from an array objects based on a async operator. I've tried following cases, but getting a Type error.
Tried Method 1
newObjectArray = await Promise.all(objectAray.reduce(async (result, el) => {
const asyncResult = await someAsyncTask(el);
if (asyncResult) {
result.push(newSavedFile);
}
return result;
}, []));
Tried Method 2
newObjectArray = await Promise.all(objectAray.reduce(async (prevPromise, el) => {
const collection = await prevPromise;
const asyncResult = await someAsyncTask(el);
if (asyncResult) {
prevPromise.push(newSavedFile);
}
collection.push(newSavedFile);
return collection;
}, Promise.resolve([])));
Error
'TypeError: #<Promise> is not iterable',
' at Function.all (<anonymous>)',
In your first try, result is a promise as all async functions evaluate to a promise when called, so you have to await result before you can push to the array, and then you don't need the Promise.all:
newObjectArray = await objectAray.reduce(async (result, el) => {
const asyncResult = await someAsyncTask(el);
if (asyncResult) {
(await result).push(newSavedFile);
}
return result;
}, []);
But I'd guess that it is way faster to just filter afterwards:
newObjectArray = (await Promise.all(objArray.map(someAsyncTask))).filter(el => el);

Javascript implement async to get result of long-running process into array

I have an array of files that I am adding data to which conceptually works like this:
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
newarray = [];
for (let f of filearray) {
newstuff = 'newstuff';
newarray.push([f, newstuff]);
}
console.log(newarray)
// Returns expected array of arrays
However, what I need to do is make newstuff = slow_promise_function(f); that involves lots of processing. How do I get the value from that promise function into the array?
Ideally I'd like to use the new async feature in ES2017.
Update:
These answers are helping me understand the problems (and solutions):
https://stackoverflow.com/a/37576787/1061836
https://stackoverflow.com/a/43422983/1061836
You could use Promise.all which returns a single Promise that resolves when all of the promises have resolved:
let loadData = async () => {
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
try {
let ops = filearray.map(f => slow_promise_function(f));
let newarray = await Promise.all(ops);
// TODO: use newarray
} catch (err) {
console.log(err);
}
}
loadData();
async/await is a nice way to accomplish this, as you suspected:
console.log('Starting...');
let files = ['file1.txt', 'file2.txt', 'file3.txt'];
Promise.all(files.map(async f => [f, await slow_promise_function(f)]))
.then(files => console.log('got results: ', files));
function slow_promise_function(file) {
return new Promise(res => setTimeout(_ => res(`processed ${file}`), 500));
}
Well that can simply be done with Promises more info on this link Promises.
const newStuffFunc = async () => {
try {
let newStuff = await slow_promise_function(f);
// new stuff is a promise. so you either do it with await again or with then/catch
let data = await newStuff;
} catch (e){
}
}
const slow_promise_function = (url) => {
return new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue); // fulfilled
// or
// reject("failure reason"); // rejected
});
};
This link can show you more usability of async/promises into javascript.

Categories