JavaScript - Yield until next function is finished - javascript

Let's say I have a simple Node.js app that has these methods in another file:
module.exports = {
completeQuest(data) {
// Code here
},
killMonster(data) {
// Also code here
},
};
And they are not AJAX commands. These just manipulate some data within the app. I'd export as such in allActions.js:
const player = require('./playerActions');
module.exports = {
player,
};
and of course later, in main JS file const actions = require('./allActions');
So, generally, I'd do:
actions.player.killMonster();
actions.player.completeQuest();
But I want them to act one after the other. I know I can do async/await, but I'm not doing any AJAX calls, so would a Promise still be the best way?
What about using a yield function? Would that be good? I'm looking for opinions is all. Thank you.

I'm going to assume that killMonster and completeQuest perform asynchronous actions. (You've said they're "not ajax", but your question suggests they're not synchronous, either.)
Yes, this is a use case for promises, either explicit promises or those provided by async functions. Have killMonster and completeQuest return a promise (either by doing so explicitly or making them async functions), and then either:
actions.player.killMonster()
.then(
() => actions.player.completeQuest()
)
.catch(error => {
// Handle error
});
or, within an async function:
try {
await actions.player.killMonster();
await actions.player.completeQuest();
} catch (error) {
// Handle error
}
Here's a simple async/await example using setTimeout to provide the asynchronous part:
const delay = (ms, ...args) =>
new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
// Stand-ins for the actions
const player = {
actions: {
async killMonster() {
await delay(500, "monster killed");
},
async completeQuest() {
await delay(800, "quest complete");
}
}
};
// Top-leve async function (see my answer here:
// https://stackoverflow.com/questions/46515764/how-can-i-use-async-await-at-the-top-level
(async () => {
try {
console.log("Killing monster...");
await player.actions.killMonster();
console.log("Monster killed; completing question...");
await player.actions.killMonster();
console.log("Quest complete");
} catch (e) {
// Deal with error (probably don't just dump it to the console like this does)
console.error(e);
}
})();

Related

Proper way to Abort (stop) running async/await function?

There has been other topics on SE, but most of them are dated 5 years ago. What is the current, up-to-date approach to cancel await call in JS? i.e.
async myFunc(){
let response = await oneHourLastingFunction();
myProcessData(response);
}
at specific moment application decides it no longer want to wait that oneHourLastingFunction, but it is stuck in "await". How to cancel that? Any standard ways of cancelation-tokens/abortControllers for promises?
Canceling an asynchronous procedure is still not a trivial task, especially when you need deep cancellation and flow control. There is no native solution at the moment. All you can do natively:
pass AbortController instance to each nested async function you want to make cancellable
subscribe all internal micro-tasks (requests, timers, etc) to the signal
optionally unsubscribe completed micro-tasks from the signal
call abort method of the controller to cancel all subscribed micro-tasks
This is quite verbose and a tricky solution with potential memory leaks.
I can just suggest my own solution to this challenge- c-promise2, which provides cancelable promises and a cancelable alternative for ECMA asynchronous functions - generators.
Here is an basic example (Live Demo):
import { CPromise } from "c-promise2";
// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
// optionally just for logging
this.onCancel(() =>
console.log("oneHourLastingFunction::Cancel signal received")
);
yield CPromise.delay(5000); // this task will be cancelled on external timeout
return "myData";
});
async function nativeAsyncFn() {
await CPromise.delay(5000);
}
async function myFunc() {
let response;
try {
response = await oneHourLastingFunction().timeout(2000);
} catch (err) {
if (!CPromise.isCanceledError(err)) throw err;
console.warn("oneHourLastingFunction::timeout", err.code); // 'E_REASON_TIMEOUT'
}
await nativeAsyncFn(response);
}
const nativePromise = myFunc();
Deeply cancellable solution (all functions are cancellable) (Live Demo):
import { CPromise } from "c-promise2";
// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
yield CPromise.delay(5000);
return "myData";
});
const otherAsyncFn = CPromise.promisify(function* () {
yield CPromise.delay(5000);
});
const myFunc = CPromise.promisify(function* () {
let response;
try {
response = yield oneHourLastingFunction().timeout(2000);
} catch (err) {
if (err.code !== "E_REASON_TIMEOUT") throw err;
console.log("oneHourLastingFunction::timeout");
}
yield otherAsyncFn(response);
});
const cancellablePromise = myFunc().then(
(result) => console.log(`Done: ${result}`),
(err) => console.warn(`Failed: ${err}`)
);
setTimeout(() => {
console.log("send external cancel signal");
cancellablePromise.cancel();
}, 4000);

Javascript: callback function vs normal function call

I am new to JavaScript and I am so confused with callbacks vs normal function calls and when to use callbacks in a real scenario.
Can someone please tell me, how both the below implementations are different from each other? or a real case scenario that makes a callback useful than a normal function call?
Using the normal function call
function getDetails(){
setTimeout(() => {
console.log("DETAILS")
}, 2000);
}
function getUser(){
setTimeout(() => {
console.log("USER");
getDetails(); // Normally calling the function
}, 3000);
}
getUser();
Using Callback
function getDetails(){
setTimeout(() => {
console.log("DETAILS")
}, 2000);
}
function getUser(callback){
setTimeout(() => {
console.log("USER");
callback(); // Calling the function
}, 3000);
}
getUser(getDetails);
There is no difference technically in the two examples you showed (assuming you won't modify getDetails before it is called). What makes it useful is that the function that calls the callback doesn't have to know the exact function to call (and could be used with different ones as needed). For instance, something like an event listener or the callback to Array.prototype.map only makes sense with the callback pattern.
But the scenario you showed ideally wouldn't use either - it should be restructured to use async/await:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
async function getDetails (user) {
await sleep(2000)
console.log('DETAILS', user)
return 'some details'
}
async function getUser (userId) {
await sleep(3000)
console.log('USER', userId)
return 'some user'
}
async function main () {
const user = await getUser(123)
const details = await getDetails(user)
console.log('got these details:', details)
}
main().catch(e => console.error('Failed to fetch data:', e))
// If you are in an environment that supports top-level await,
// you can just use `await main()` instead
I added some more example stuff to illustrate a real use case.

Is it a good 'idea' to prevent nested promise with async / await?

I have some difficulties with a nested promise (below).
For now I'm using an async function in the catch to trigger authentication Errors.
But is it really a good way and isn't there a better way ?
The function have to send a POST request. If an authentication Error is thrown then login(), else throw the error.
If login() is fulfilled : retry the POST (and then return the results), else throw the error;
function getSomeData() {
return post('mySpecialMethod');
}
function login() {
const params = { /* some params */ }
return post('myLoginMethod', params).then(result => {
/* some processing */
return { success: true };
});
}
const loginError = [1, 101];
function post(method, params, isRetry) {
const options = /* hidden for brevity */;
return request(options)
// login and retry if authentication Error || throw the error
.catch(async ex => {
const err = ex.error.error || ex.error
if (loginError.includes(err.code) && !isRetry) { // prevent infinite loop if it's impossible to login -> don't retry if already retried
await login();
return post(method, params, true)
} else {
throw err;
}
})
// return result if no error
.then(data => {
// data from 'return request(options)' or 'return post(method, params, true)'
return data.result
});
}
Use
getSomeData.then(data => { /* do something with data */});
I'd suggest that for complex logic at least you use the async/await syntax.
Of course .then() etc is perfectly valid, however you will find the nesting of callbacks awkward to deal with.
My rule (like a lot of things in programming) is use context to make your decision. .then() works nicely when you're dealing with a limited number of promises. This starts to get awkward when you're dealing with more complex logic.
Using async / await for more involved logic allows you to structure your code more like synchronous code, so it's more intuitive and readable.
An example of two approaches is shown below (with the same essential goal). The async / await version is the more readable I believe.
Async / await also makes looping over asynchronous tasks easy, you can use a for loop or a for ... of loop with await and the tasks will be performed in sequence.
function asyncOperation(result) {
return new Promise(resolve => setTimeout(resolve, 1000, result));
}
async function testAsyncOperationsAwait() {
const result1 = await asyncOperation("Some result 1");
console.log("testAsyncOperationsAwait: Result1:", result1);
const result2 = await asyncOperation("Some result 2");
console.log("testAsyncOperationsAwait: Result2:", result2);
const result3 = await asyncOperation("Some result 3");
console.log("testAsyncOperationsAwait: Result3:", result3);
}
function testAsyncOperationsThen() {
return asyncOperation("testAsyncOperationsThen: Some result 1").then(result1 => {
console.log("testAsyncOperationsThen: Result1:", result1);
return asyncOperation("testAsyncOperationsThen: Some result 2").then(result2 => {
console.log("testAsyncOperationsThen: Result2:", result2);
return asyncOperation("testAsyncOperationsThen: Some result 3").then(result3 => {
console.log("testAsyncOperationsThen: Result3:", result3);
})
})
})
}
async function test() {
await testAsyncOperationsThen();
await testAsyncOperationsAwait();
}
test();
... But is it really a good way and isn't there a better way ?
No it's not a good idea because it hurts code readability.
You're mixing 2 interchangeable concepts, Promise.then/catch and async/await. Mixing them together creates readability overhead in the form of mental context switching.
Anyone reading your code, including you, would need to continuously switch between thinking in terms of asynchronous flows(.then/.catch) vs synchronous flows (async/await).
Use one or the other, preferably the latter since it's more readable, mostly because of it's synchronous flow.
Although I don't agree with how you're handling logins, here's how I would rewrite your code to use async/await and try...catch for exception handling:
function getSomeData() {
return post('mySpecialMethod')
}
async function login() {
const params = { } // some params
await post('myLoginMethod', params)
return { success: true }
}
const loginError = [1, 101]
async function post(method, params, isRetry) {
const options = {} // some options
try {
const data = await request(options)
return data.result
} catch (ex) {
const err = ex.error.error || ex.error
if (err) throw err
if (loginError.includes(err.code) && !isRetry) {
await login()
return post(method, params, true)
}
throw err
}
}
I obviously cannot/didn't test the above.
Also worth exploring the libraries which provides retry functionalities.
something like https://www.npmjs.com/package/async-retry
Generally this is not a big problem. You can chain/encapsulate async calls like this.
When it comes to logic it depends on your needs. I think the login state of a user should be checked before calling any API methods that require authentication.

How can I order a series of api calls from the frontend?

I have a series of API calls I need to make from a 'user profile' page on my app. I need to prioritize or order the calls when I land on the component.
I have tried using async-await on the componentDidMount lifecycle method but when the first call fails, the rest do not get called.
...
async componentDidMount() {
await user.getGameStats();
await user.getFriendsList();
await user.getPlayStyle();
}
...
Despite ordering the calls, I would like them to still execute regardless of whether the preceding calls failed.
You need to account for rejected promises. If you don't catch the error it will stop execution. Just add a catch() block to each function that may fail.
function a(){
return new Promise((r,f)=>{
console.log('a');
r();
});
}
function b(){
return new Promise((r,f)=>{
console.log('b');
f(); // rejecting this promise
});
}
function c(){
return new Promise((r,f)=>{
console.log('c');
r();
});
}
async function d(){
throw new Error('Something broke');
}
(async ()=>{
await a().catch(()=>console.log('caught a'));
await b().catch(()=>console.log('caught b'));
await c().catch(()=>console.log('caught c'));
await d().catch(()=>console.log('caught d'));
})();
It's a dirty solution, but you can do something like this:
user.getGameStats().then({res => {
callGetFriendsList();
}).catch({err =>
callGetFriendsList();
});
function callGetFriendsList() {
user.getFriendsList().then(res => {
user.getPlayStyle();
}).catch(err => {
user.getPlayStyle();
});
}
The ideal and good way would be to call all of them at the same time asynchronously if they are not dependent on the response of the previous request.
Just add an empty catch at the end of each API call as following
async componentDidMount() {
await user.getGameStats().catch(err=>{});
await user.getFriendsList().catch(err=>{});
await user.getPlayStyle().catch(err=>{});
}
I'd catch to null:
const nullOnErr = promise => promise.catch(() => null);
async componentDidMount() {
const [gameStats, friendsList, playStyle] = await Promise.all([
nullOnErr(user.getGameStats()),
nullOnErr(user.getFriendsList()),
nullOnErr(user.getPlayStyle())
]);
//...
}
I also used Promise.all to run the calls in parallel as there seems to be no dependency between the calls.

node.js put event right after another event

Let's say I have two async events, both need to i/o with remote exchange.
placeOrder()
cancelOrder()
Both events fire in async way, which means cancelOrder can be called before placeOrder return. Tricky part is I need the placeOrder to return an Order ID first otherwise there is no way to call cancelOrder, so I need some way to block the cancelOrder event right until placeOrder returns, and the blockage cannot be too long otherwise the Order may be executed, so loop/timeout/frequent checking doesn't work here.
Any idea?
You would use a Promise for that. If your functions already return a promise, you can simply chain the both functions using then()
placeOrder().then(val => cancelOrder(val));
If they do not, you can put them inside a new Promise
function foo() {
return new Promise((resolve, reject) => {
// do stuff
resolve('<result of placeOrder here>');
});
}
function bar(val) {
return new Promise((resolve, reject) => {
// do stuff
resolve('whatever')
})
}
and call
foo()
.then(value => bar(value))
.then(console.log);
If you are able to use ES2017, the you can use async functions. For example, I'm going to assume that your functions perform some sort of request to the database using fetch or axios since you haven't specified. Then you can write placeOrder and cancelOrder like so:
const placeOrder = async () => {
try {
const response = await fetch('/place_order');
// Do something with the response
} catch (err) {
// Handle error
}
};
const cancelOrder = async () => {
try {
const response = await fetch('/cancel_order');
// Do something with the response
} catch (err) {
// Handle error
}
};
const someFunction = async () => {
await placeOrder();
await cancelOrder();
};

Categories