Promise.all is resolving conditional promises - javascript

i have written a function that will either return the input promise of an empty promise based on a condition
export const promiseUtility = (promise, condition) => {
if (condition) {
return promise;
} else {
return new Promise(resolve => resolve());
}
};
when i am calling this function such that
const arr= [
promiseUtility({
promise: getUsers(req, type, id),
condition: req.body.shouldGetUsers
}),
promiseUtility({
promise: getObjects(req, type, id),
condition: req.body.shouldGetObjects,
];
const [users =[], objects =[]] = await Promise.all(arr)
The problem that i am facing is when a condition like shouldGetUsers is false, even then the original promise i.e. getUsers(req) is getting resolved. It looks like this is because i have called that getUsers function. How can i deal with such situation

If you don't want a promise to resolve, you must not create it in the first place. But you already create a promise when you pass getUsers(req, type, id) as the promise argument to your promiseUtility function.
I suggest you use a conditional operator instead, which will only execute the getUsers function if the condition is true:
const arr = [
req.body.shouldGetUsers ? getUsers(req, type, id) : Promise.resolve(),
req.body.shouldGetObjects ? getObjects(req, type, id) : Promise.resolve()
];

Agreed with Heiko's answer. But, i figured out if we want to make a generic utility function for that then we can do it like this.
export const promiseUtility = (promise, condition) => {
if (condition) {
return promise();
} else {
return new Promise(resolve => resolve());
}
};
const arr= [
promiseUtility({
promise: () => getUsers(req, type, id),
condition: req.body.shouldGetUsers
}),
promiseUtility({
promise: () => getObjects(req, type, id),
condition: req.body.shouldGetObjects,
})
];
const [users =[], objects =[]] = await Promise.all(arr)

Related

Promises being executed when they shouldn't be

I have several db mutations that I would like to execute all at once, instead of synchronously. The problem that I'm running into, is that when I try to push these promises into an array, they execute.
What am I doing wrong here? I've also tried pushing anonymous functions, like this,
promises.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
but they aren't execute during Promise.all.
import * as React from "react";
import "./styles.css";
const someDbMutation1 = async ({ someForeignKey }) => {
return await new Promise((resolve) => {
console.log("should not enter");
return setTimeout(() => {
resolve("aa");
}, 2000);
});
};
const someDbMutation2 = async ({ someParameter }) =>
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 2000)
);
export default function App() {
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
init();
}, []);
const init = React.useCallback(async () => {
const promises = [
someDbMutation1({ someForeignKey: "10" }),
someDbMutation2({ someParameter: "abc" })
];
// await Promise.all(promises);
setLoaded(true);
}, []);
return <div className="App">{loaded && <div>done</div>}</div>;
}
I would expect these promises in the promises array to be executed during a call to Promise.all, but clearly that's not the case here. I've noticed this only recently, when I passed null as a value to a foreign key, at which point the key constraint in my db picked it up and threw an error.
Now I'm worried, because I frequently use a promises array and loop over db objects and push mutation queries into promises -- this means, that each request is executed twice! I'm not sure what I'm missing here.
For the first part of your question where you say that it's not executing:
promises.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
it's because you are pushing an anonymous function not a promise - They are two different things. Based on your array name, I think expected behavior would be for you to do this instead:
promises.push(
someDbMutation1({ someForeignKey: "10" })
)
If you want all promises to be executed at a single point in time then you could do this instead:
queries.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
/ ** -- some point later -- ** /
const promises = queries.map(q => q()) // Execute queries
const results = await Promise.all(promises) // Wait for queries to finish
In addition, you have a misunderstanding on how Promise.all works here:
I would expect these promises in the promises array to be executed during a call to Promise.all
Promise.all doesn't execute the promises, it waits for the promises to resolve. There is a reference here.
So in this part:
const promises = [
someDbMutation1({ someForeignKey: "10" }),
someDbMutation2({ someParameter: "abc" })
];
You are actually executing the functions so that if you were to console.log the promises array it would look something like this:
[
Promise (unresolved),
Promise (unresolved)
];
And then after await Promise.all(), the promises array would look like this:
[
Promise (resolved: value),
Promise (resolved: value)
];
Issue 1: Promises must be awaited in the block that actually awaits for them.
const someDbMutation1 = async ({ someForeignKey }) => {
return new Promise((resolve) => {
console.log("should not enter");
return setTimeout(() => {
resolve("aa");
}, 2000);
});
};
const someDbMutation2 = async ({ someParameter }) =>
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 2000)
);
The problem is you are executing the promises. You should instead add them into an array as anonymous functions that call your function with the parameters you want. So this should look like this. :
const init = React.useCallback(async () => {
const promises = [
async () => someDbMutation1({ someForeignKey: "10" }),
async () => someDbMutation2({ someParameter: "abc" })
];
await Promise.all(promises);
setLoaded(true);
}, []);
I hope this is the answer you are looking for.

Conditional/dynamic array promise all

I have a function with an array of promises, that array can have from 1 to X promises.
Those promises enter into the array based on conditionals.
I want to be able to distinguish from which API comes each result, and I can't realise a clean way to do it
let promises = [];
if (false) {
let promise1 = request(toUrl);
promises.push(promise1);
}
if (true) {
let promise2 = request(toUrl);
promises.push(promise2);
}
if (false) {
let promise3 = request(toUrl);
promises.push(promise3);
}
if (true) {
let promise4 = request(toUrl);
promises.push(promise4);
}
try {
let result = await Promise.all(promises);
} catch (error) {
console.log(error);
}
So, if everything goes ok result will be an array of results. Not knowing which one of the conditionals was true, how do I know if result[0] is the result of promise1, promise2 or promise3?
You can just add to the response of your request(url) another information about the promise like
const promise1 = request(url).then(res => ({ res: res, promise: 'promise1' }))
and at the Promise.all() you will get values of the promises in the above form and can detect which promises were resolved.
Example
const promises = [];
if(true) {
const promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => ({ res: res, promise: 'promise1' }));
promises.push(promise1);
}
if(false) {
const promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2').then(res => ({ res: res, promise: 'promise2' }));
promises.push(promise2);
}
Promise.all(promises).then(res => console.log(res));
In my opinion we can simplify the complexity of the problem by using following code -
let promises = [];
let truthyValue = true,
falsyvalue = true;
let [promise1, promise2, promise3, promise4] = await Promise.all([
truthyValue ? request(toUrl) : Promise.resolve({}),
truthyValue ? request(toUrl) : Promise.resolve({}),
falsyValue ? request(toUrl) : Promise.resolve({}),
falsyValue ? request(toUrl) : Promise.resolve({})
]);
// promise1 will be called only when truthyValue is set
if (promise1) {
// do something
}
// promise2 will be called only when truthyValue is set
if (promise2) {
// do something
}
// promise3 will be called only when falsyValue is set
if (promise3) {
// do something
}
// promise4 will be called only when falsyValue is set
if (promise4) {
// do something
}
I used an object map of promises with a name key in order to identify which resolve corresponds to which promise.
const promises = {};
const mapResolveToPromise = res => Object.fromEntries(
Object.entries(promises).map(([key], index) => [key, res[index]])
);
promises.promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1');
promises.promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2');
Promise.all(Object.values(promises))
.then(mapResolveToPromise)
.then(res => {
console.log(res.promise1.url);
console.log(res.promise2.url);
});
I had a similar problem, always a different quantity of async functions to call.
What I didn't want was to start the promises work before promise.all().
So I collected function pointers in an array.
eg:
async function first() {
return new Promise((resolve)=> setTimeout(resolve,1000,99));
}
async function second() {
return new Promise((resolve)=> setTimeout(resolve,1500,100));
}
let x = [first, second];
// x is transformed into an array with then executed functions
await Promise.all(x.map(x=>x()))
Result is:
[
99,
100
]
hopefully this helps and I understood the mentioned problem ... :)
Why all the pushes, you can construct arrays inline.
doPromiseStuff = async ({ thing = true }) => {
const urls = ['', '', '', ''];
return await Promise.all([
thing ? request(urls[1]) : request(urls[2]),
thing ? request(urls[3]) : request(urls[4])
]);
}

How to use Array.prototype.filter with async?

Background
I am trying to filter an array of objects. Before I filter, I need to convert them to some format, and this operation is asynchronous.
const convert = () => new Promise( resolve => {
setTimeout( resolve, 1000 );
});
So, my first try was to do something like the following using async/await:
const objs = [ { id: 1, data: "hello" }, { id: 2, data: "world"} ];
objs.filter( async ( obj ) => {
await convert();
return obj.data === "hello";
});
Now, as some of you may know, Array.protoype.filter is a function which callback must return either true or false. filter is synchronous. In the previous example, I am returning none of them, I return a Promise ( all async functions are Promises ).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
So as one can assume, the code before doesn't really work... That assumption is correct.
Problem
To make filter work with an async function, I checked stackoverflow and found this topic:
Filtering an array with a function that returns a promise
Unfortunately, the chosen answer is overly complex and uses classes. This won't do for me. I am instead looking for a more simple solution, using simple functions with a functional approach.
There is one solution at the very end, using a map with a callback to simulate a filter:
https://stackoverflow.com/a/46842181/1337392
But I was hoping to fix my filter function, not to replace it.
Questions
Is there a way to have an async function inside a filter?
If not, what is the simplest replacement I can do?
There is no way to use filter with an async function (at least that I know of).
The simplest way that you have to use filter with a collection of promises is to use Promise.all and then apply the function to your collection of results.
It would look something like this:
const results = await Promise.all(your_promises)
const filtered_results = results.filter(res => //do your filtering here)
Hope it helps.
Adapted from the article How to use async functions with Array.filter in Javascript by Tamás Sallai, you basically have 2 steps:
One that creates the conditions for an object to pass
One that receives the objects and returns true or false according to conditions
Here's an example
const arr = [1, 2, 3, 4, 5];
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const asyncFilter = async (arr, predicate) => {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}
const asyncRes = await asyncFilter(arr, async (i) => {
await sleep(10);
return i % 2 === 0;
});
console.log(asyncRes);
// 2,4
Use Scramjet fromArray/toArray methods...
const result = await scramjet.fromArray(arr)
.filter(async (item) => somePromiseReturningMethod(item))
.toArray();
as simple as that - here's a ready example to copy/paste:
const scramjet = require('../../');
async function myAsyncFilterFunc(data) {
return new Promise(res => {
process.nextTick(res.bind(null, data % 2));
});
}
async function x() {
const x = await scramjet.fromArray([1,2,3,4,5])
.filter(async (item) => myAsyncFilterFunc(item))
.toArray();
return x;
}
x().then(
(out) => console.log(out),
(err) => (console.error(err), process.exit(3)) // eslint-disable-line
);
Disclamer: I am the author of scramjet. :)
Build a parallel array to your array which you want to call filter on.
Await all of the promises from your filter func, in my eg, isValid.
In the callback in filter, use the 2nd arg, index, to index into your parallel array to determine if it should be filtered.
// ===============================================
// common
// ===============================================
const isValid = async (value) => value >= 0.5;
const values = [0.2, 0.3, 0.4, 0.5, 0.6];
// ===============================================
// won't filter anything
// ===============================================
const filtered = values.filter(async v => await isValid(v));
console.log(JSON.stringify(filtered));
// ===============================================
// filters
// ===============================================
(async () => {
const shouldFilter = await Promise.all(values.map(isValid));
const filtered2 = values.filter((value, index) => shouldFilter[index]);
console.log(JSON.stringify(filtered2));
})();
This behavior makes sense since any Promise instance has a truthy value, but it's not intuitive at a glance.
This answer uses library iter-ops, which handles iterable objects, and supports async filtering:
import {pipeAsync, filter, toAsync} from 'iter-ops';
// your input data:
const objs = [{id: 1, data: 'hello'}, {id: 2, data: 'world'}];
const i = pipeAsync(
objs,
filter(async value => {
await convert(); // any async function
return value.data === 'hello'; // filtering logic
})
);
for await(const a of i) {
console.log(a); // filtered data
}
P.S. I'm the author of iter-ops.
Reduce method can mimic filter and can operate with promises.
const isPositiveNumberAsync = async (number) => number >= 0;
const filterPositiveNumbersAsync = async (numbers) => numbers?.reduce(async (accumulatorPromise, number) => {
const accumulator = await accumulatorPromise;
if (await isPositiveNumberAsync(number)) {
return [...accumulator, number];
}
return accumulator;
}, Promise.resolve([])) || [];
(async () => {
// no numbers argument provided
console.log(await filterPositiveNumbersAsync());
// an empty argument list provided
console.log(await filterPositiveNumbersAsync([]));
// ok, but no positive numbers provided
console.log(await filterPositiveNumbersAsync([-1,-2,-3]));
// ok, positive numbers filtered.
console.log(await filterPositiveNumbersAsync([0,1,-1,-3,2,-2]));
})();
Array.prototype.asyncFilter =function( filterFn) {
const arr = this;
return new Promise(function(resolve){
const booleanArr = [];
arr.forEach(function (e) {
booleanArr.push(filterFn(e))
})
Promise.all(booleanArr).then(function (booleanArr) {
const arr2 = arr.filter(function (e, i) {
return booleanArr[i]
})
resolve(arr2)
})
})
}
/** use it like this**/
const arr=[1,2,3]
arr.asyncFilter(async e=>{}).then(...)
You can use Promise.filter from Bluebird that works similarly to Array.filter but it supports async & await.
Add asyncFilter as an extension to Array:
#available(macOS 10.15.0, *)
extension Array where Element: Any {
public func asyncFilter(closure: (Element) async -> Bool) async -> Array {
var result = [Element]()
for item in self {
if await closure(item) {
result.append(item)
}
}
return result
}
}
Usage:
result = await result.asyncFilter { item in
if <item match> {
return true
}
}

use forEach() with promises while access previous promise results in a .then() chain?

I have the following functions with promises:
const ajaxRequest = (url) => {
return new Promise(function(resolve, reject) {
axios.get(url)
.then((response) => {
//console.log(response);
resolve(response);
})
.catch((error) => {
//console.log(error);
reject();
});
});
}
const xmlParser = (xml) => {
let { data } = xml;
return new Promise(function(resolve, reject) {
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data,"text/xml");
if (xmlDoc.getElementsByTagName("AdTitle").length > 0) {
let string = xmlDoc.getElementsByTagName("AdTitle")[0].childNodes[0].nodeValue;
resolve(string);
} else {
reject();
}
});
}
I'm trying to apply those functions for each object in array of JSON:
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
I came up with the following solution:
function example() {
_.forEach(array, function(value) {
ajaxRequest(value.url)
.then(response => {
xmlParser(response)
.catch(err => {
console.log(err);
});
});
}
}
I was wondering if this solution is acceptable regarding 2 things:
Is it a good practice to apply forEach() on promises in the following matter.
Are there any better ways to pass previous promise results as parameter in then() chain? (I'm passing response param).
You can use .reduce() to access previous Promise.
function example() {
return array.reduce((promise, value) =>
// `prev` is either initial `Promise` value or previous `Promise` value
promise.then(prev =>
ajaxRequest(value.url).then(response => xmlParser(response))
)
, Promise.resolve())
}
// though note, no value is passed to `reject()` at `Promise` constructor calls
example().catch(err => console.log(err));
Note, Promise constructor is not necessary at ajaxRequest function.
const ajaxRequest = (url) =>
axios.get(url)
.then((response) => {
//console.log(response);
return response;
})
.catch((error) => {
//console.log(error);
});
The only issue with the code you provided is that result from xmlParser is lost, forEach loop just iterates but does not store results. To keep results you will need to use Array.map which will get Promise as a result, and then Promise.all to wait and get all results into array.
I suggest to use async/await from ES2017 which simplifies dealing with promises. Since provided code already using arrow functions, which would require transpiling for older browsers compatibility, you can add transpiling plugin to support ES2017.
In this case your code would be like:
function example() {
return Promise.all([
array.map(async (value) => {
try {
const response = await ajaxRequest(value.url);
return xmlParser(response);
} catch(err) {
console.error(err);
}
})
])
}
Above code will run all requests in parallel and return results when all requests finish. You may also want to fire and process requests one by one, this will also provide access to previous promise result if that was your question:
async function example(processResult) {
for(value of array) {
let result;
try {
// here result has value from previous parsed ajaxRequest.
const response = await ajaxRequest(value.url);
result = await xmlParser(response);
await processResult(result);
} catch(err) {
console.error(err);
}
}
}
Another solution is using Promise.all for doing this, i think is a better solution than looping arround the ajax requests.
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
function example() {
return Promise.all(array.map(x => ajaxRequest(x.url)))
.then(results => {
return Promise.all(results.map(data => xmlParser(data)));
});
}
example().then(parsed => {
console.log(parsed); // will be an array of xmlParsed elements
});
Are there any better ways to pass previous promise results as
parameter in then() chain?
In fact, you can chain and resolve promises in any order and any place of code. One general rule - any chained promise with then or catch branch is just new promise, which should be chained later.
But there are no limitations. With using loops, most common solution is reduce left-side foldl, but you also can use simple let-variable with reassign with new promise.
For example, you can even design delayed promises chain:
function delayedChain() {
let resolver = null
let flow = new Promise(resolve => (resolver = resolve));
for(let i=0; i<100500; i++) {
flow = flow.then(() => {
// some loop action
})
}
return () => {
resolver();
return flow;
}
}
(delayedChain())().then((result) => {
console.log(result)
})

Plain es6 promise; how to do n arbitrary operations?

I have two simple methods
// send a single command
sendCommand(command) {
return new Promise((resolve, reject) => {
this._commands.write(command, (response) => {
response === '?' : reject(command) : resolve(command);
});
});
}
// has to send multiple commands and wait for the result
sendArray(name, array) {
let bits = _.chunk(array, 4);
_.each(bits, (bit, index) => {
this.sendCommand(`QD ${name}[]${bits.join('\r')}\\`);
});
}
However, is there any way for this array be sent through the promises iteratively with plain es6 promises? Eg:
// for every bit in bits
this.sendCommand(bit1)
.then(() => { this.sendCommand(bit2) })
// ...
.then(() => { this.sendCommand(bitN) })
.catch(console.log);
Something like
let allBitsPromise = _.chunk(array, 4).reduce(function (p, bit) {
return p.then(() => sendCommand(bit));
}, Promise.resolve());
would work.
The least obvious part of this (to me, anyway) is that if the callback passed to then returns a promise p then the original promise is resolved when p is. So a promise like
Promise.resolve().then(() => {
return new Promise((resolve, reject) => setTimeout(resolve, 2000))
});
Is only resolved when the promise returned by the callback is resolved by setTimeout.
Keep a reference to the promise returned by the function and chain the .then call on it:
let promise = Promise.resolve();
_.each(bits, (bit, index) => {
promise = promise.then(() => this.sendCommand(`QD ${name}[]${bits.join('\r')}\\`));
});
promise.catch(error => console.log(error));
Note that this will send the data sequentially, which I assume is what you want.

Categories

Resources