Chaining promises with promise all results in unexpected execution order - javascript

Why does the following code print baz, done before 1, 2, 3?
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(console.log("baz"));
foo().then(console.log("done"));

You are executing console.log() immediately instead of passing it to callback function in .then(). This will do it:
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(() => console.log("baz"));
foo().then(() => console.log("done"));

You probably forgot to embed the console.log into arrow functions so their execution is properly deferred:
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(() => console.log("baz"));
foo().then(() => console.log("done"));

Related

How can I avoid promise chain drilling?

My promise chain looks like this:
PromiseA()
.then((A) => PromiseB(A))
.then((B) => PromiseC(B))
...
.then((X) => PromiseY(X))
.then((Y) => PromiseZ(Y, A))
How do I use parameter A in the last promise without drilling through all the promises like so:
PromiseA()
.then((A) => Promise.all[A, PromiseB(A)])
.then(([A, B]) => Promise.all[A, PromiseC(B)])
...
.then(([A, X]) => Promise.all[A, PromiseY(X)])
.then(([A, Y]) => PromiseZ(A, Y))
Refactor your function to async/await:
async function fun() {
const A = await PromiseA();
const B = await PromiseB(A);
const C = await PromiseC(B);
// ...
const Y = await PromiseY(A, B, C);
const Z = await PromiseZ(Y, A);
}
You can nest the chain inside the first .then() callback so that A is still in scope.
PromiseA()
.then((A) =>
PromiseB(A)
.then(PromiseC)
.then(PromiseD)
.then(PromiseE)
...
.then((Y) => PromiseZ(Y, A)
);
You can always use a variable to store it.
let a;
PromiseA()
.then(A => (a = A, PromiseB(A))
.then(B => PromiseC(B))
...
.then(X => PromiseY(X))
.then(Y) => PromiseZ(a, Y));

Multiple same Async Await function only Triggered the Last One

const fun = (x) => {
console.log(`Meth... ${x}`);
setTimeout(() => {
onReceive(`Fun Received: ${x}`);
}, 100);
return x;
}
const publish = () => {
return new Promise(resolve => {
window.onReceive = (token) => {
resolve(token);
}
});
}
const Inst = () => {
const getAs = async(x) => {
console.log(`Getting As ----- ${x}`);
const res = await publish(fun(x));
console.log(`Res is '${res}' ---- ${x}`);
}
return {
getAs,
}
}
const inst = Object.freeze(Inst());
const a = async() => {
await inst.getAs(1);
await inst.getAs(2);
await inst.getAs(3);
await inst.getAs(4);
await inst.getAs(5);
};
a();
const b = async() => {
await inst.getAs(4);
await inst.getAs(5);
await inst.getAs(6);
await inst.getAs(7);
};
b();
// Comment or remove `b()` to see the different result in Console
I got confused why the function only called the last async await (b() from the example). Why inside of a() seems like didn't get called at all??
If I comment / remove b(), all inside of a() will get call.
Need helps / explanation to resolve such issue 🙏.

How to wait async data to start sync function

I get some data from an api call and set them in a state. Then I use this state variable in another function to filter some data. When the user opens the interface for the first time the data doesnt show because the sync function gets the empty data from the state.
Here is the code :
const [evQuestion, setEvQuestion] = useState();
const [answers, setAnswers] = useState();
const getEvaluationsQuestionsByOrganizations = async (evalId) => {
const response = await apiStandarts.get(`/evaluation-questions?organization_evaluation=${evalId}`);
setEvQuestion(response.data);
};
const evAnswers = () => {
const evAnswers = questions.map(q => {
return evQuestion?.map(ev => {
return q.question_options.find(i => i.id === ev.questOptionId)
});
});
const filterAnswers = evAnswers.map(q => {
return q?.filter(Boolean)
})
const answersToObject = filterAnswers.map(item => {
return convertArrayToObject(item)
});
const arr = {...answersToObject}
const obj2 = Object.fromEntries(
Object.entries(arr).map(([key, value]) => [key, value])
)
const obj3= Object.values(obj2).map(item => {
return {[item.question]: {...item}}
})
const savedAnswers = convertArrayToObject(obj3);
console.log(savedAnswers)
setAnswers(savedAnswers)
}
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
evAnswers();
}, [])
I've tried to wrap the evAnswers function in a settimeout function but with no luck. How can I achieve this, any ideas?
Try adding another useEffect hook that depends on evQuestion state.
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
}, []);
useEffect(() => {
evAnswers();
}, [evQuestion]);
the function getEvaluationsQuestionsByOrganizations(..) is defined as async function, but you are using it synchronously, in that case you should call your codes as below:
useEffect(() => {
const fetchedDataAPI = async () => {
return await getEvaluationsQuestionsByOrganizations();
};
fetchedDataAPI
.then(res => { evAnswers();})
.catch(err => {..});
;
}, []);

Chain async fallbacks and print their errors only if all fail

I want to invoke an async method, followed by a series of async fallback methods until one of them succeeds.
If all invocations fail, then I want all of their errors printed. Otherwise, if even one succeeds, the errors should not be printed.
This is what I want:
tryX()
.catch(x => tryXFallback1()
.catch(xf1 => tryXFallback2()
.catch(xf2 => tryXFallback3()
.catch(xf3 => tryXFallback4()
// ...
.catch(xf4 => Promise.reject([x, xf1, xf2, xf3, xf4]))))));
But I'm not a fan of the indentation. Accumulating the errors in a variable outside the scope of the catch clauses also seems messy:
let errors = [];
tryX()
.catch(x => {
errors.push(x);
return tryXFallback1();
})
.catch(xf1 => {
errors.push(x);
return tryXFallback2();
})
.catch(xf2 => {
errors.push(x);
return tryXFallback3();
})
.catch(xf3 => {
errors.push(x);
return tryXFallback4();
})
// ...
.catch(xf4 => Promise.reject(errors));
Lastly, I thought I could do some sort of for loop instead but that seems even uglier e.g.:
let methods = [tryX, tryFallback1, tryFallback2, tryFallback3, tryFallback4, /*...*/];
let errors = [];
for (let x of methods)
try {
return await x();
} catch (e) {
errors.push(e);
}
if (errors.length === methods.length)
return Promise.reject(errors);
Does anyone know of a more elegant approach?
The loop you have seems fine. I would probably stick with it as it already works. However, here is an alternative:
function tryWithFallbacks(main, ...fallbacks) {
return fallbacks.reduce(
(p, nextFallback) => p.catch( //handle errors
err => nextFallback() //try using the fallback
.catch(e => Promise.reject(err.concat(e))) //propagate rejection reasons
//on failure by adding to
//the array of errors
),
main() //seed the process with the main task
.catch(err => Promise.reject([err])) //ensure there is an array of errors
);
}
const a = tryWithFallbacks(
() => Promise.resolve(42)
);
test(a, "a"); //42
const b = tryWithFallbacks(
() => Promise.reject("oops"),
() => Promise.resolve("it's fine")
);
test(b, "b"); //"it's fine"
const c = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3")
);
test(c, "c"); //["oops1", "oops2", "oops3"]
const d = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("finally!")
);
test(d, "d"); //"finally!"
const e = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("penultimate try successful!"),
() => Promise.reject("this is not reached")
);
test(e, "e"); //"penultimate try successful!"
//a simple function to illustrate the result
function test(promise, name) {
promise
.then(result => console.log(`[${name}] completed:`, result))
.catch(errorResult => console.log(`[${name}] failed:`, errorResult));
}
It's Array#reduce()-ing all the promises into one and making sure of the sequential order. If any succeed, you just get a single successful result. On failure, the error response is added to an array of all errors and passed forward via the rejection flow of promises.
This currently does require that all the functions that produce a promise are thunks - they take no input.
For reference, an equivalent operation using await and a loop would be:
async function tryWithFallbacks(...tasks) {
const errors = [];
for (const task of tasks) {
try {
const result = await task();
return result;
} catch (err) {
errors.push(err);
}
}
return errors;
}
const a = tryWithFallbacks(
() => Promise.resolve(42)
);
test(a, "a"); //42
const b = tryWithFallbacks(
() => Promise.reject("oops"),
() => Promise.resolve("it's fine")
);
test(b, "b"); //"it's fine"
const c = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3")
);
test(c, "c"); //["oops1", "oops2", "oops3"]
const d = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("finally!")
);
test(d, "d"); //"finally!"
const e = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("penultimate try successful!"),
() => Promise.reject("this is not reached")
);
test(e, "e"); //"penultimate try successful!"
//a simple function to illustrate the result
function test(promise, name) {
promise
.then(result => console.log(`[${name}] completed:`, result))
.catch(errorResult => console.log(`[${name}] failed:`, errorResult));
}
Use Promise.any():
Promise.any([tryXFallback1, tryXFallback2, tryXFallback3]).then(result => console.log(result)).catch(err => console.log(err.errors))
This solves everything you need:
Works if any async of the passed array is resolved
Logs all of the errors that were thrown (The object that is passed to the catch callback of any, has property .errors)

Sequential function calls

I have an array of asynchronous functions, it is necessary to call in order and the result of the call of the previous function is passed into the arguments. How can this be done approximately?
// lets say we have a function that takes a value and adds 20 to it asynchronously
const asyncPlus20 = num => Promise.resolve(num+a)
const arr = [asyncPlus20, asyncPlus20]
let res = 0 // some starting value
for (const f of arr) res = await f(res)
// res is now 20
One of the best way for Array of Async functions is to use For...of.
Run the below snippet in the console. >> Also includes the argument passing
const twoSecondsPromise = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('2000_'), 2000);
})
};
const threeSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '3000_'), 3000);
})
};
const fiveSecondsPromise = (val) => {
return new Promise((resolve) => {
setTimeout(() => resolve(val + '5000_'), 5000);
})
};
(async function () {
const asyncFunctions = [twoSecondsPromise, threeSecondsPromise, fiveSecondsPromise];
let result;
for (const file of asyncFunctions) {
result = await file(result);
console.log(result);
}
})();

Categories

Resources