How to make result of Promise all in order - javascript

Question:
the output of case1 is from 0 to 4 which is in order while
the output of case2 is in random.
I know the reason why case1's result is in order is that the request is send after the result of previous request comes.
In case2 the request will not wait the result of previous request.
my question is that is there a way to retain the result of case2 in order too?
case1
const main = async () => {
const arr = Array.of(...[1,2,3,4,5])
for (let i=0;i<arr.length;i++) {
console.log(`request:${i}`)
const res = await request(i)
console.log(res)
}
console.log("next step")
}
const request = (i:number):Promise<number> => {
return new Promise<number>(((resolve, reject) => {
setTimeout(()=>{
resolve(i)
},Math.random() * 1000)
}))
}
output1
closure
request:0
0
request:1
1
request:2
2
request:3
3
request:4
4
next step
case2
const main = async () => {
const arr = Array.of(...[1,2,3,4,5])
await Promise.all(arr.map(async (v,i) => {
console.log(`request:${v}`)
const res = await request(v)
console.log(`result:${res}`)
console.log(res)
})).catch((e) => {
})
console.log("next step")
}
const request = (i:number):Promise<number> => {
return new Promise<number>(((resolve, reject) => {
setTimeout(()=>{
resolve(i)
},Math.random() * 1000)
}))
}
main()
output2
request:1
request:2
request:3
request:4
request:5
result:4
4
result:5
5
result:1
1
result:3
3
result:2
2
next step

Promise.all() returns an array of the results in the same order; they just won't resolve in order. You could return the response within your request promise, and...
const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3]);
Or if you wanted to iterate over an array...
const results = await Promise.all([promise1, promise2, promise3]);

Promise All should be array of request or promise, in map() should return request.
try this
const main = () => {
const arr = Array.of(...[1,2,3,4,5])
Promise.all(arr.map((v,i) => {
console.log(`request:${v}`)
return request(v)
})).then((res)=>{
res.forEach((val)=>{
console.log(`result:${val}`)
})
}).catch((e) => {
})
console.log("next step")
}
const request = (i)=> {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(i)
}, Math.floor(Math.random() * 10)*1000)
})
}
main()

Related

Print two array one after another(with interval) in javascript

I have two array :
numbers:[1,2,3,4]
letters: ["a","b","c","d"]
I want to print as the numbers array first with time interval of 3 sec and then print letters array.
output should be: 1(3sec interval) 2(3sec interval) 3(3sec interval) 4(3sec interval) 5(3sec interval) a b c d.
I tried with following code:
const result = document.getElementById("frame")
const numbers = [1,2,3,4], letters = ["a","b","c","d"]
const function1 = () =>
letters.forEach((c, i) => setTimeout(() => console.log(c)));
const function2 = () =>
numbers.forEach((c, i) => setTimeout(() => console.log(c), i * 3000));
async function main(){
await function1();
await function2();
}
main();
const numbers = [1, 2, 3, 4]
const letters = ['a', 'b', 'c', 'd']
const wait = value => new Promise(resolve => setTimeout(() => resolve(), value))
const function1 = async () => {
numbers.forEach(async (item, i) => {
await wait(3000 * i)
console.log(item)
})
await wait(3000 * numbers.length - 1)
letters.forEach((item) => {
console.log(item)
})
}
function1()
You can do this
const numbers = [1,2,3,4], letters = ["a","b","c","d"]
const function1 = () => new Promise((resolve) => {
letters.forEach((c, i) => setTimeout(() => console.log(c), i * 3000));
setTimeout(resolve, letters.length * 3000)
})
const function2 = () =>
numbers.forEach((c, i) => setTimeout(() => console.log(c)));
async function main(){
await function1();
await function2();
}
main();
If you're just logging all the letters at once the function doesn't need to be async. You can just return the joined array from function2.
function1 does need to be async but that's not what happening in your code. Because setTimeout doesn't behave well inside async functions you need to set up a promise-based delay, and then use that inside the function.
const numbers = [1, 2, 3, 4];
const letters = ['A', 'B', 'C', 'D'];
// Resolve a promise after a given time
function delay(time) {
return new Promise(res => {
return setTimeout(() => res(), time);
});
}
function function1(arr) {
// Return a promise that is awaited
return new Promise(res => {
// Loop over the array
async function loop(i) {
// Log the element
console.log(arr[i]);
// Call the delay function
await delay(3000);
// And then work out if you need
// to call `loop` again, or resolve the promise
if (arr.length - 1 === i) {
res();
} else {
loop(++i);
}
}
// Call loop for the first time
loop(0);
});
}
function function2(arr) {
return arr.join(' ');
}
async function main() {
await function1(numbers);
console.log(function2(letters));
}
main();
For the record, another simple example where the output is awaited. This assumes your final goal of outputting will be the same for both arrays, just without delays. In case the actual output method is async, you can await that too, but keep a single entry point for both arrays
const numbers = [1,2,3,4], letters = ["a","b","c","d"];
async function output(arr,delayAfterEach){
for(element of arr){
console.log(element);
if(delayAfterEach)
await new Promise(r=>setTimeout(r,delayAfterEach));
}
}
async function main(){
await output(numbers,3000);
await output(letters);
}
main();

Not able to set item with the key in the final object

The value of videoProgress is not returned in the final object document, i want videoProgress to be returned along with the documentList item, but i am not getting it, i have set item.videoProgress too but not getting it in response:
const document = await Promise.all(documentList.map(async (item) => {
const videoCount = await studentProgressModel.find({ gradeId: item.gradeId, userId : item._id, type: 'VIDEO'}).countDocuments();
const totalVideoCount = await videoModel.find({ gradeId: item.gradeId, mediumId: item.mediumId, status: 'ACTIVE' }).countDocuments();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return item;
}));
The issue here is not what anyone in the comments above is discussing, the issue is that you are not actually returning a promise from your documentList.map, but instead are just writing an async function there. Array.map will not wait for any asynchronous behavior, so as soon as it hits an await it returns an empty result.
This can be fixed by instead returning promises in your map:
const document = await Promise.all(documentList.map((item) => { // Removed async here, map is synchronous
// Here we *immediately* return a promise so that map can return that to the promise.all
return new Promise(async (resolve) => {
// Now we can do all our async stuff and promise.all will wait for it :)
const videoCount = await studentProgressModel.find({ gradeId: item.gradeId, userId : item._id, type: 'VIDEO'}).countDocuments();
const totalVideoCount = await videoModel.find({ gradeId: item.gradeId, mediumId: item.mediumId, status: 'ACTIVE' }).countDocuments();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return resolve(item); // have to use resolve here with our result to resolve the promise.
});
}));
There are other ways to do this that don't involve an explicit new-promise wrapper, but I figured it made the promise behavior more obvious
Edit: Here's an example which can be run directly here in your browser, I have only replaced the queries with async random number generators.
function randomInteger() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(Math.floor(Math.random() * 100));
}, 1);
});
}
async function main() {
let documentList = [{}, {}, {}];
const document = await Promise.all(documentList.map((item) => {
return new Promise(async (resolve) => {
const videoCount = await randomInteger();
const totalVideoCount = await randomInteger();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return resolve(item);
});
}));
console.log('Document results:', document);
}
main();

How can I be sure that it first starts with the "Startpoint" function call and waits until this one is resolved to start the next function call?

Within a map function, I want to call an asynchronous function if the name is "Startpoint" and the same function if the name is "Question". These functions call an API (from dialogflow). I have the problem that the second function call overtakes the first one.
How can I be sure that it first starts with the "Startpoint" function call and waits until this one is resolved to start the next function?
Here is the code:
const editorJSON = {1: "Startpoint", 2: "Answer", 3: "Question"};
Object.keys(editorJSON).map((key, index) => {
if (editorJSON[key].name === "Startpoint") {
saveNodeasIntentinDialogflow(someArgument);
if (editorJSON[key].name === "Question") {
saveNodeasIntentinDialogflow(someArgument);
And here is the function saveNodeasIntentinDialogflow (simplified):
const saveNodeAsIntentinDialogflow = async (someParameter) => {
try {
const res = await axios.post(
`https://dialogflow.googleapis.com/v2/projects/agent/intents:batchUpdate`)}
One working solution is to split them up into two different maps
const editorJSON = {
1: "Startpoint",
2: "Answer",
3: "Question"
};
//console logged at end to show output order
const order = [];
//longer async
async function mockLonger(param) {
return await new Promise(function(resolve, reject) {
setTimeout(() => resolve(order.push(param)), 2000);
})
}
//shorter async
async function mockShorter(param) {
return await new Promise(function(resolve, reject) {
setTimeout(() => resolve(order.push(param)), 1000);
})
}
//async map
async function mapArray(array) {
console.log("processing...");
const resultsA = array.map(async(key, index) => {
if (editorJSON[key] === "Startpoint") await mockLonger("longer");
})
//process longer call first
await Promise.all(resultsA)
const resultsB = array.map(async(key, index) => {
if (editorJSON[key] === "Question") await mockShorter("shorter");
})
//then shorter call
await Promise.all(resultsB)
console.log("Output Order:", order)
}
mapArray(Object.keys(editorJSON));
Why does it have to be within a map function? Just iterate the array and await.
const editorJSON = {1: "Startpoint", 2: "Answer", 3: "Question"};
(async () => {
for (const value of Object.values(editorJSON)) {
await saveNodeasIntentinDialogflow(value);
}
})();
async function saveNodeasIntentinDialogflow(name) {
await new Promise(r => setTimeout(r, 3000 * Math.random()));
console.log(`${name} is done`);
}

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

Change to async/await syntax

I have some code that print in console a number series start for 1,
continuing for 2 and so on. I've done it with promises but now I want to change my promise script to async/await mode, but It doesn't work.
What I tried was this:
const alwaysThrows = () => {
throw new Error("OH NOES");
};
const iterate = (integer) => {
console.log(integer);
return integer + 1;
};
const prom = Promise.resolve(iterate(1));
const manageOk = async () => {
let result = await prom;
console.log(result);
}
manageOk()
but I dont know how get the rest of numbers.
This is my original code:
const alwaysThrows = () => {
throw new Error("OH NOES");
};
const iterate = (integer) => {
console.log(integer);
return integer + 1;
};
const prom = Promise.resolve(iterate(1));
prom
.then((value) => iterate(value))
.then(iterate)
.then(iterate)
.then(iterate)
.then(alwaysThrows)
.then(iterate)
.then(iterate)
.then(iterate)
.catch(e => console.log(e.message));
const manageOk = async (val) => {
return await iterate(val);
}
manageOk(1)
.then(res => manageOk(res))
.then(manageOk)
.then(manageOk)
.then(manageOk)

Categories