javascript - reduce array of async and sync functions - javascript

I am trying to reduce an array of both asynchronous and synchronous methods. I have two synchronous methods and one asynchronous methods that does some basic formatting an object. Since I have a mix of asynchronous and synchronous methods, I am using async/await with my reduce. The problem is my formatName method returns the error Cannot read property 'toUpperCase' of undefined because the person parameter passed in is a promise. I thought that since i use await in my reduce, the callback should return the actual value instead of a promise:
const results = await fn(finalResult);
My code works fine if i take out the synchronous methods from my array. But I need to reduce an array of both synchronous and asynchronous methods. Does anyone have tips on how I can get around this? My full code is below.
const formatName = (person) => {
person.name = person.name.toUpperCase();
return person;
}
const formatAge = (person) => {
person.age += 10;
return person;
}
// Async function
const formatLocation = async (person) => {
await new Promise(resolve => setTimeout(resolve, 1000));
person.location = `${person.location.toLocaleLowerCase()}`
return person;
}
const initialPerson = {
name: "john",
age: 35,
location: 'USA'
}
const formattedStagesWithAsync = [
formatLocation, // asynchronous
formatAge, // synchronous
formatName //synchronous
];
const process = async () => {
const formattedPerson = await formattedStagesWithAsync.reduce(async (finalResult, fn) => {
const results = await fn(finalResult);
return results;
}, initialPerson);
console.log(`Formatted person - ${JSON.stringify(formattedPerson, null, 2)}`);
}
process();

Async functions always return promises, so on subsequent iterations of the .reduce callback, the accumulator will be a Promise that you need to wait to resolve first.
While you could do
const accumVal = await finalResult
at the beginning of the callback, this would be a lot simpler by avoiding reduce entirely IMO.
const process = async () => {
let person = initialPerson;
for (const fn of formattedStagesWithAsync) {
person = await fn(person);
}
In the case that each of your functions returns the original object, like in the case here, that simplifies to:
const process = async () => {
for (const fn of formattedStagesWithAsync) {
await fn(initialPerson);
}
(you're mutating the initialPerson in your original code too)

Promise return async value but dont use async and await option for Promise, Just return it
Promise docs
const promiseHandler = () => {
return new Promise((done, reject) => {
setTimeout(() => {
done("hello");
});
});
}
promiseHandler().then(v => {
console.log(v); // hello
});

Related

Why can I not return an array of objects in an async function?

In node.js I am trying to get a list of bid and ask prices from a trade exchange website (within an async function). Within the foreach statement I can console.info() the object data(on each iteration) but when I put all of this into an array and then return it to another function it passes as 'undefined'.
const symbolPrice = async() => {
let symbolObj = {}
let symbolList = []
await bookTickers((error, ticker) => {
ticker.forEach(symbol => {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolObj = {
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
}
console.info(symbolObj);
}
symbolList.push(symbolObj)
});
const results = Promise.all(symbolList)
return results;
});
}
const symbolPriceTest = async() => {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
I have tried pretty much everything I can find on the internet like different awaits/Promise.all()'s. I do admit I am not as familiar with async coding as I would like to be.
So, if the basic problem here is to call bookTickers(), retrieve the asynchronous result from that call, process it and then get that processed result as the resolved value from calling symbolPrice(), then you can do that like this:
const { promisify } = require('util');
const bookTickersP = promisify(bookTickers);
async function symbolPrice(/* declare arguments here */) {
let symbolList = [];
const ticker = await bookTickersP(/* fill in arguments here */);
for (let symbol of ticker) {
if (symbol.symbol.toUpperCase().startsWith(starts.toUpperCase())) {
symbolList.push({
symbol: symbol.symbol,
bid: symbol.bidPrice,
ask: symbol.askPrice
});
}
}
return symbolList;
}
async function symbolPriceTest() {
const res = await symbolPrice(null, 'ETH', true);
console.log(res)
}
Things to learn from your original attempt:
Only use await when you are awaiting a promise.
Only use Promise.all() when you are passing it an array of promises (or an array of a mixture of values and promises).
Don't mix plain callback asynchronous functions with promises. If you don't have a promise-returning version of your asynchronous function, then promisify it so you do (as shown in my code above with bookTickersP().
Do not guess with async and await and just throw it somewhere hoping it will do something useful. You MUST know that you're awaiting a promise that is connected to the result you're after.
Don't reuse variables in a loop.
Your original implementation of symbolPrice() had no return value at the top level of the function (the only return value was inside a callback so that just returns from the callback, not from the main function). That's why symbolPrice() didn't return anything. Now, because you were using an asynchronous callback, you couldn't actually directly return the results anyway so other things had to be redone.
Just a few thoughts on organization that might be reused in other contexts...
Promisify book tickers (with a library, or pure js using the following pattern). This is just the api made modern:
async function bookTickersP() {
return new Promise((resolve, reject) => {
bookTickers((error, ticker) => {
error ? reject(error) : resolve(ticker);
})
});
}
Use that to shape data in the way that the app needs. This is your app's async model getter:
// resolves to [{ symbol, bid, ask }, ...]
async function getSymbols() {
const result = await bookTickersP();
return result.map(({ symbol, bidPrice, askPrice }) => {
return { symbol: symbol.toUpperCase(), bid: bidPrice, ask: askPrice }
});
}
Write business logic that does things with the model, like ask about particular symbols:
async function symbolPriceTest(search) {
const prefix = search.toUpperCase();
const symbols = await getSymbols();
return symbols.filter(s => s.symbol.startsWith(prefix));
}
symbolPriceTest('ETH').then(console.log);

How to execute asynchronous tasks in order

I'm currently experimenting with asynchronous functions in Javascript and I stumbled upon a case where I wanted to execute a collection of asynchronous functions in the order that they are placed in an array.
I also want the possibility to pass an argument to an asynchronous function or not. With my current solution, the two functions with a passed argument are executed first and at the same time, then the other two functions are executed one after the other.
const asyncOne = async (value = "no value passed to async 1") => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000);
});
};
const asyncTwo = async (value = "no value passed to async 2") => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(value);
resolve();
}, 1000);
});
};
const sequence = async tasks => {
const resolve = Promise.resolve(null);
await tasks.reduce(
(promise, task) =>
promise.then(() => (typeof task === "function" ? task() : task)),
resolve
);
};
(async () => {
const res = await sequence([asyncOne("first"), asyncTwo, asyncOne, asyncTwo("last")]);
})();
Expected output:
"first"
"no value passed to async 2"
"no value passed to async 1"
"last"
Actual output:
"first"
"last"
"no value passed to async 2"
"no value passed to async 1"
As I mentioned in my comment, you should not mix new Promise with async – there's no need to, and you'll just confuse yourself and any future readers.
Either way, an async function will begin to execute synchronously, which is why you see your sequence (both the first and last timeouts begin at the same moment). To fix this, you'd need to use a function that calls the function that returns a promise, like so:
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function asyncOne(value = "async1 default") {
await delay(1000);
console.log(value);
}
async function asyncTwo(value = "async2 default") {
await delay(1000);
console.log(value);
}
async function sequence(tasks, init = undefined) {
let value = init;
for (let task of tasks) {
if (typeof task === "function") value = await task(value);
else value = await task;
}
return value;
}
(async () => {
await sequence([
() => asyncOne("first"),
asyncTwo,
asyncOne,
asyncTwo.bind(this, "last"), // can also use `bind` instead of an anonymous arrow function
]);
})();
This implementation also lets you chain values from one promise to another, and allows for an initial value to be passed to the first promise-returning function, á la
await sequence([asyncTwo], "hello!");
asyncOne("first") and asyncTwo("last") call the functions and execute them immediately, so it prevents you from executing them in order.
Instead, write asyncOne.bind(this, "first"), which does not execute the function immediately and allows you to execute it later.
(async () => {
const functions = [
asyncOne.bind(this,"first"),
asyncTwo,
asyncOne,
asyncTwo.bind(this, "last")
];
for(let f of functions){
await f()
}
})();

How to execute a array of synchronous and asynchronous functions in Node.js

I have config like JSON where we can define any JavaScript functions inside. Now I have execution function which would take that array of functions and execute. How can I do that?
const listOfFuncs = [
{
"func1": (...args) => console.log(...args)
},
{
"func2": async (...args) => {
return await fetch('some_url');
}
}
]
function execute() {
// how to execute the above array of functions now ?
}
// Should this be called as await execute()?
execute();
As you can see one function sync & another function as async & await. Defining everything function as async & await seems bad ( creating a lot of new promises ) + I can't define all function as synchronous also.
Thanks for your answer in advance.
You can use Promise.all() to resolve an array of promises.
Values other than promises will be returned as-is
const listOfFuncs = [
() => 45,
async () => new Promise(resolve => {
setTimeout(() => resolve(54), 100);
}),
() => Promise.resolve(34)
];
// Convert to list of promises and values
const listOfMixedPromisesAndValues = listOfFuncs.map(func => func());
// Promise.all returns a Promise which resolves to an array
// containing all results in the same order.
Promise.all(listOfMixedPromisesAndValues).then(result => {
// Logs: [45, 54, 34] after 100ms
console.log(result)
});
There is no native function to resolve an object containing promises.
However some libraries implement alternative Promise API, to make it easier to use more complex pattern (cancellation, races, ...). The most well known is Bluebird.
It implements a Promise.props methods which almost do what you want: http://bluebirdjs.com/docs/api/promise.props.html
var Promise = require("bluebird");
Promise.props({
pictures: getPictures(),
comments: getComments(),
tweets: getTweets()
}).then(function(result) {
console.log(result.tweets, result.pictures, result.comments);
});
You should simply treat all your functions as potentially returning a promise. No need to distinguish them, just await the result. The execution will carry on immediately if it was not a thenable (read: non-promise) value. So just write
async function execute() {
for (const func of listOfFuncs) {
await func();
}
}
If you want the asynchronous tasks to run concurrently, just collect all values into an array and pass them to Promise.all. It deals with non-promise elements just fine as well.
async function execute() {
await Promise.all(listOfFuncs.map(func => func()));
}
Solution without promises.
Use process.nextTick(callback) or setImmediate
const listOfFuncs = [
{
"func3": setImmediate(execute, "setImmadiate executes after process.nextTick")
},
{
"func2": process.nextTick(execute, "process.nextTick executes after sync function but before setImmediate")
},
{
"func1": execute
}
]
function execute() {
console.log("executing...", arguments[0]);
}
execute(listOfFuncs);
// results:
// executing...[...]
// executing...process.tick
// executing...setImmediate...
In this example, you create an array of the executed functions and use the Promise.all() with a map to get all promisses if the function results. (in a case of an async function the function returns a promise-value, which you can await)
function isPromise(promise) {
return !!promise && typeof promise.then === 'function'
}
let functions = [
(() => {})(),
(async () => {})()
];
await Promise.all(functions.map(function_result => (isPromise(function_result) ? function_result : undefined))
Maybe this is a solution for you:
await Promise.all([
(async () => {
//Your Function here
})(),
]);
Ideally having everything async would be cleaner. But if not, this would work :
async function execute(...args) {
for (let i = 0; i < listOfFuncs.length; i++) {
if (listOfFuncs[i].constructor.name === "AsyncFunction") {
await listOfFuncs[i](...args);
} else {
listOfFuncs[i](...args);
}
}
}

Promise then() method is not firing as expected? [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
In the code below, I expect mark-1 to fire before mark-2. I'm guessing that I'm not using await and async correctly some where.
I'm pretty sure that this line:
const things = await fs.promises.readdir(folder);
and this line
const stats = await fs.promises.stat(path);
are correct as we are awaiting the file system to respond.
I'm not conerned with error checking or cross-platform code as of yet, just getting the promises to work correctly
// Libraries
const fs = require('fs');
// API
getThings('_t/').then(() => {
console.log('mark-2')
})
// Accesses the file system and returns an array of files / folders
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
}
// Gets statistics for each file/folder
async function getStats (thing, folder) {
const path = folder + thing;
const stats = await fs.promises.stat(path);
console.log('mark-1');
}
The problem is that you are using async and await in the forEach call, and this doesn't work as you would expect.
The forEach method doesn't really care about the return value of the callback function (in this case the promise that getStats returns).
You should map the things array to promises, and use Promise.all:
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
const promises = things.map(thing => getStats(thing, folder));
return await Promise.all(promises);
}
Note that this will execute the promises "in parallel", and not sequentially.
If you want to execute the promises secuentially, one by one, you can either, "reduce" the promise array, or use a conventional loop (for, for-of).
EDIT:
Let me try to clarify why using an async callback function with forEach doesn't work.
Attempt #1: De-sugarizing it:
In the original example we have something like this:
// ...
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
// ...
If we separate the callback from the forEach, we have:
const callback = async (thing)=>{
await getStats(thing, folder);
};
things.forEach(callback);
If we "desugarize" the async function:
const callback = function (thing) {
return new Promise((resolve, reject) => {
try {
getStats(thing, folder).then(stat => /*do nothing*/);
} catch (err) {
reject(err);
}
resolve();
});
};
things.forEach(callback);
Marking a function with async ensures the function will return always return a promise, regardless its completion, if the function reaches its execution without a explicit return value, the promise will be resolved with undefined, if it returns some value, the promise will resolve to it, and finally if something within the function throws, the promise will be rejected.
As you can see the problem is that the promises are just not being awaited by anything, and also they are not resolving to any value. The await placed in the callback does actually nothing with the value, just as in the above I'm doing .then and doing nothing with the value.
Attempt 2: Implementing a simple forEach function:
function forEach(array, callback) {
for(const value of array) {
callback(value);
}
}
const values = [ 'a', 'b', 'c' ];
forEach(values, (item) => {
console.log(item)
});
The above forEach is an oversimplification of the Array.prototype.forEach method, just to show its structure, in reality the callback function is called passing the array as the this value, and passing three arguments, the current element, the current index, and again the array instance, but we get the idea.
If we would like to implement an async forEach function, we would have to await the callback call:
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
async function forEachAsync(array, callback) {
for(const value of array) {
await callback(value);
}
}
(async () => {
await forEachAsync(values, async (item) => {
console.log(await sleep(item.time, item.value))
});
console.log('done');
})()
The above forEachAsync function will iterate and await item by item, sequentially, normally you don't want that, if the async functions are independent, they can be done in parallel, just as I suggested in first place.
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
(async () => {
const promises = values.map(item => sleep(item.time, item.value));
const result = await Promise.all(promises);
console.log(result);
})()
And as you can see, even if the promises are executed in parallel, we get the results in the same order as the promises where in the array.
But the difference between this example and the first one is that this one takes only 300ms (the longest promise to resolve), and the first one takes 600ms (300ms + 200ms + 100ms).
Hope it makes it clearer.

Using async in a promise because of db-calls. How can I fix this antipattern?

I have a Firebase Function which sends back data from databases. The problem is sometimes I have to return data all of 3 collections, sometimes only need from 1 and sometimes 2 of them. But this is an antipattern. How can I improve my code?
Right now I'm creating a function, which returns a promise, in which I'm using await for getting db values and this is wrapped in try{} block.
module.exports.getList = (uid, listType) => new Promise(async (resolve, reject) => {
let returnValue = [];
try {
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
} catch (e) {
return reject(e);
}
return resolve(returnValue);});
How should I modify this snippet in order to not be an antipattern? Or is it not because of the try-catch block?
You can make the getList function async instead, without new Promise or try/catch:
module.exports.getList = async (uid, listType) => {
const returnValue = [];
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
return returnValue;
};
Calling it will return a Promise that rejects with an error if there's an asynchronous error, or it will resolve to the desired array.
Note that unless there's a good reason to await each call in serial, you can use Promise.all instead, so that the requests go out in parallel, and make the code a lot more concise in the process:
module.exports.getList = (uid, listType) => Promise.all(
['alist', 'blist', 'clist']
.filter(name => listType.contains(name[0]))
.map(name => db.collection(name).doc(uid).get())
);

Categories

Resources