Promises being executed when they shouldn't be - javascript

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.

Related

How to turn multiples promises dependent on each other's response into await and make sure all requests fire

I have an array of objects that has 3 items inside it. I'm iterating over it and during each loop i need to make 3 API calls that all depend on each other's response. So in total there should be 9 API requests completed.
const arrayOfObjects = [
{
first: 'thing',
second: 'thing',
third: 'thing'
},
{
fourth: 'thing',
fifth: 'thing',
sixth: 'thing'
},
{
seventh: 'thing',
eight: 'thing',
ninth: 'thing'
},
]
const makeModuleAndBatteryPromises = () => {
arrayOfObjects.map((obj) => {
createModules(obj.first).then((response) => {
createBattery(response, obj.second).then(res => {
assignAssets(res, obj.third).then(res => 'assignment done!');
});
});
});
}
makeModuleAndBatteryPromises();
So looking at above code, it seems like i truly only have control over the first 3 API Calls in the 1st loop of arrayOfObjects. As i have assignAssets(res).then(res => 'assignment done!'); which will allow me to do some operation like refresh page or redirect once the 3rd promise is resolved in the first loop.
However i need to do some operation on the 9th/final promise. Here is what i tried trying to make it async await.
const makeModuleAndBatteryPromises = async () => {
const promises = arrayOfObjects.map(async obj => {
const firstResponse = await createModules(obj.first);
const secondResponse = await createModules(firstResponse, obj.second);
await assignAssets(secondResponse);
});
await Promise.all(promises)
.then(res => //do some action after all 9 promises resolved)
}
makeModuleAndBatteryPromises()
Not quite achieving what i was expecting it to, can some1 please tell me what i'm missing?
if I understand correctly, you want the resolved value of the final assignAssets?
You've possibly confused yourself with a mix of async/await and .then
const makeModuleAndBatteryPromises = async() => {
const promises = arrayOfObjects.map(async obj => {
const firstResponse = await createModules(obj.first);
const secondResponse = await createModules(firstResponse, obj.second);
return assignAssets(secondResponse);
});
const res = await Promise.all(promises);
const finalValue = res.at(-1); // or res[res.length-1];
// do things with finalValue
}
makeModuleAndBatteryPromises()

How to use promise in order to wait for a function to execute

I have an async piece of javascript code that need to execute and then the next statements can be executed
function getEventData() {
return new Promise(resolve => {
eventList.forEach((event) => {
db.collection('Events').doc(event)?.collection('registeredStudents')?.get()
.then((querySnapshot) => {
eventData.push({"id": event, "number": querySnapshot.size})
})
})
resolve();
})
}
getEventData().then(console.log(eventData))
eventList is an array with 17 elements and it list through the database around 17 times, that is inefficient but I had no choice so had to do like that. Anyways in the console I get an empty array logged. How do I solve that. PS: I am new to async javascript and promises.
You can use Promise.all():
function getEventData() {
return new Promise(async (resolve) => {
await Promise.all(
eventList.map(async (event) => {
let querySnapshot = await db.collection('Events').doc(event)?.collection('registeredStudents')?.get()
eventData.push({"id": event, "number": querySnapshot.size})
})
);
resolve();
})
}
getEventData().then(console.log(eventData))
This is a simple demo of how to use promises in a loop. The async operation I am performing is "sleeping" (aka nothing happens until everything has "slept")..
const sleepTimes = [1, 2, 3];
const promises = [];
sleepTimes.forEach(time => {
const promise = new Promise((resolve, reject) => {
return setTimeout(resolve, time * 1000, time * 1000);
});
promises.push(promise);
});
console.log(promises);
Promise.all(promises).then((values) => {
document.body.innerHTML = values;
});
Which would mean your function should look something like:
function getEventData() {
const promises = [];
eventList.forEach((event) => {
// I assume this already returns a promise, so there's no need to wrap it in one
const promise = db.collection("Events").doc(event)?.collection("registeredStudents")?.get();
promises.push(promise);
});
return Promise.all(promises);
}
getEventData().then((values) => console.log(values));
The correct way to deal with your problem is not using new Promise with resolve, yet by using async await.
When you await a promise, it waits for it to resolve and returns the result (not a promise!) before continuing to the next line.
A function awaiting a promise returns a promise when called.
Promise.all combines multiple promises to one, so you can wait for multiple promises to resolve all at once, and have them run at the same time (async).
async function getEventData(eventList) {
try {
const eventData = [];
await Promise.all(eventList.map(async event => {
const querySnapshot = await db.collection('Events').doc(event)?.collection('registeredStudents')?.get();
eventData.push({"id": event, "number": querySnapshot.size});
}));
console.log(eventData)
} catch (exception) {
// error handling
}
}

How to wait for promises resolution instead of using Promise.all?

I have a situation where I'm looping over some data and I need to perform an asynchronous operation per each array element. I don't want to await the result inside the loop. Instead I want to simply start off the promises execution from the loop. After the loop is done I want to wait until all promises resolved and then return some result.
The code is below:
const t1 = delay => new Promise(resolve => {
setTimeout(() => {
console.log('from t1')
resolve()
}, delay)
})
const promises = []
const results = []
const array = [1 ,2]
array.forEach((element, index) => {
const temp = t1((index + 1) * 1000).then(() => results.push(5))
promises.push(temp)
})
const mainFunc = async () => {
const bigPromise = () => new Promise(resolve => {
if (results.length === 2) {
resolve()
}
})
await bigPromise()
console.log(results)
}
mainFunc()
I thought I could create another promise bigPromise which will check if the array length is 2. This way I will know that all promises resolved. However this doesn't work. How can I wait for some notification in Javascript? I wouldn't want to use setInterval for this problem.

Test failing after adding then and catch to promise

I have the following (simplified for the sake of scoping down problem) code:
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises(async (c) => {
const results = await Promise.all(pushPromises())
c.success(results);
});
My test mocks those firstPromise and secondPromise to check if they were called with the right arguments. This works (assume mock set up is properly done):
jest.mock('src/firstPromise');
jest.mock('src/secondPromise');
describe('test', () => {
let firstMock;
let secondMock;
beforeEach(() => {
require('src/firstPromise').default = firstMock;
require('src/secondPromise').default = secondMock;
})
it('test', async () => {
await handlePromises(context);
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
});
Now, if I add handlers to the promises such as:
function pushPromises() {
const promises = [];
promises.push(
firstPromise('something')
.then(r => console.log(r))
.catch(e => console.log(e))
)
promises.push(
secondPromise('somethingelse')
.then(r => console.log(r))
.catch(e => console.log(e))
)
return promises;
}
The first expectation passes, but the second one doesn't. It looks like the second promise is no longer called.
How can I modify the test/code so that adding handlers on the promises don't make the test break? It looks like it is just finishing the execution on the first promise handler and does never get to the second one.
EDIT:
I have modified the function to return a Promise.all without await:
export default handlePromises(async (c) => {
return Promise.all(pushPromises())
});
But I'm having the exact same issue. The second promise is not called if the first one has a .then.
In your edit, your handlePromises is still not a promise..
Try the following. ->
it('test', async () => {
await Promise.all(pushPromises());
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
});
Your handlePromises function accepts a callback but you are handling it as it returns a promise, it is not a good way to go but you can test your method using callback as following
Modify your Test as=>
it('test', (done) => {
handlePromises(context);
context.success = (results) => {
console.log(results)
expect(firstPromiseMock).toHaveBeenCalledTimes(1);
expect(secondPromiseMock).toHaveBeenCalledTimes(1);
done();
}
});
Or modify your code as =>
function pushPromises() {
const promises = [];
promises.push(firstPromise('something'))
promises.push(secondPromise('somethingelse'))
return promises;
}
export default handlePromises = (context) => {
return Promise.all(pushPromises()).then((data) => context.success(data))
};
//with async
export default handlePromises = async (context) => {
let data = await Promise.all(pushPromises());
context.success(data)
};

Promise All retry

I know that promise.all() fails when even 1 of the promise is failed. I only want to try for failed promises and don't want to run promise.all() again.
Any recommendations on how I can achieve this in minimal way?
Promises are eager construct and model a value obtained asynchronously,
a Promise is produced using some kind of producer, like fetch for instance.
If you retain a reference to this producer then you can replay the nmechanism
that produced the Promise in the first place.
// producer function
function getData (arg) {
const result = new Promise();
return result.then(value => {
return { ok:true, value };
}, error => {
return {
ok: false,
value: error,
// retry is a function which calls the producer with the same arguments
retry: () => getData(arg)
};
})
}
Then if you have something like:
const data = [];
// Promise<{ok: boolean, value: any, retry?: function}>
// No promises will fail in this array
const asyncResults = data.map(getResults);
Promise.all(asyncResults)
.then((results) => {
const successes = results.filter(res => res.ok);
const retrys = results.filter(res => !res.ok).map(res => res.retry()); // retry all failed promises
})
Memory leaks, stack overflow: because I retain a reference to original arguments in order to retry and the algorithm is recursive there could be a memory leak. However the algorithm cannot "stack overflow":
getData calls do not get "deeper" over time (see retry definition)
the asyncrhonicity of the algorithm prevent this behaviour if a promise was never resolved
old data is properly discarded when accessing the results as const resultData = results.filter(res => res.ok).map(res => res.value);
However the algorithm could take a long time to settle if a promise keep on not getting resolved and prevent access to the rest of the values.
In an alternative I suggest you take a look at another async primitive, not yet part of the language (maybe some day) : Observables which are designed for this kind of tasks: lazy, retry-able async operations.
You may use async package and wrap all promise calls with closure with done argument.
Then simply resolve results.
const async = require('async');
const promiseAll = promises => {
return new Promise((resolve) => {
// preparing array of functions which has done method as callback
const parallelCalls = promises.map(promise => {
return done => {
promise
.then(result => done(null, result)
.catch(error => {
console.error(error.message);
done();
});
}
});
// calling array of functions in parallel
async.parallel(
parallelCalls,
(_, results) => resolve(results.filter(Boolean))
);
});
};
router.get('/something', async (req, res) => {
...
const results = await promiseAll(promises);
...
});
Or we can simply do Promise.all without using async package:
router.get('/something', async (req, res) => {
...
const results = (
await Promise.all(
promises.map(promise => {
return new Promise(resolve => {
promise.then(resolve).catch(e => resolve());
});
});
)
).filter(Boolean);
...
});

Categories

Resources