How to control flow of async functions? - javascript

I have a chain of async functions, which need to be performed in order. Yet, if one of those functions fails or takes too long, it should be retriggered a certain amount of times.
So my questions is:
What is the standard/elegant structure to have such a control flow with async functions? E.g.:
funcA()
.then(resultA => funcB(resultA)
.then(resultB => funcC(resultB)
.then(...))

You can do like this
function funcA() {
return Promise.resolve('a');
}
function funcB(data) {
return Promise.resolve(data + ' b');
}
function funcC(data) {
return Promise.resolve(data + ' c');
}
async function controlFlowExample() {
const resultA = await funcA();
const resultB = await funcB(resultA);
const resultC = await funcC(resultB);
console.log(resultC);
}
controlFlowExample();

You can use async-await to chain them neatly together:
const res_a = await funcA()
const res_b = await funcB(res_a)
const res_c = await funcC(res_b)
This is better than chaining .then since here it is easier to debug as you save more values.
Sidenote: if you do it this way, it is best to implement trycatch within your functions.
IMO it looks much neater too.

Related

How to assign variable to result of asynchronous class method that returns an object in its promise?

Looks nobody on internet describes similar problem, and similar solution is not working for me.
I am trying to scrape webpage so I created class for parser and one of the method looks like follows:
get chListUrl() {
return "http://www.teleman.pl/program-tv/stacje";
}
getChannels() {
var dict = {};
axios.get(this.chListUrl).then(function (response) {
var $ = cheerio.load(response.data);
var ile_jest_stacji = $('#stations-index a').length;
$('#stations-index a').each( (i,elem) => {
let href = $(elem).attr('href');
let kodstacji = href.replace(/\/program-tv\/stacje\//ig,'');
let nazwastacji = $(elem).text();
dict[nazwastacji]=kodstacji;
});
return dict;
}).catch(function (error) {
console.log(error);
return null;
}).finally(function() {
console.log("Koniec");
});
}
And problem is getChannels must be indirectly asynchronous because it contains axios BUT
let tm = new TM();
var a = tm.getChannels();
a is always undefined and it should be dictionary! Such construct means "assing to variable a result of execution of tm.getChannels()" so assignment should always be done AFTER whole function ends. Otherwise such syntax in language is useless because you will never be sure what value is stored in variable, and such errors are difficult to find and debug.
var a = await tm.getChannels();
NOT WORKING -> SyntaxError: await is only valid in async function (huh?)
adding async to getChannels() changes nothing.
Assing async to getChannels() and remove 'await' from assignment returns Promise{undefined} (huh?)
putting async before axios changes nothing as response is already handled by .then()
changing return dict to return await dict gives another "await is only valid in async function" (huh? axios is asynchronous)
I'm scratching my head over this for 2 weeks.
In Swift when something is return in completion handler it is assigned to variable in proper moment, why something returned by Promise not works the same way?
You need to be inside an async function to use the await expression:
The await operator is used to wait for a Promise. It can only be used inside an async function.
await operator on MDN
Example sourced from MDN:
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
Fixing your issue
class TM {
get chListUrl() {
return "http://www.teleman.pl/program-tv/stacje";
}
async getChannels() { // you need not use get syntax
let dict = {};
try { // we will be handling possible errors with try catch instead of reject
const response = await axios.get(this.chListUrl);
let $ = cheerio.load(response.data);
let ile_jest_stacji = $('#stations-index a').length;
$('#stations-index a').each( (i,elem) => {
let href = $(elem).attr('href');
let kodstacji = href.replace(/\/program-tv\/stacje\//ig,'');
let nazwastacji = $(elem).text();
dict[nazwastacji]=kodstacji;
});
return dict;
} catch(ex) {
console.log(ex);
return null
}
}
}
// let's make it work!
(async function() {
const tm = new TM()
const channels = await tm.getChannels()
// do whatever you want with channels
console.log(channels)
})()
Now, you're probably not going to call getChannels out of nowhere like this instead you will probably be inside a function that you yourself defined, you need to add the async keyword to this function. Whatever block function your code is in needs to be async.
If you want to use the async/await syntax you should remove the .then() syntax and you can resolve that way:
async getChannels() {
const response = await axios.get(this.chListUrl);
return response
}
You can learn more about async/await in the link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Can async functions accept and execute callback functions?

I've been really intrigued about async / await syntax recently and I've been experimenting with it for a while.
In this particular problem, my goal is to execute the callback function after all the promises are made, if possible.
I had no idea how to apply my ideas in real life situations so I expected the function to look like this.
function endTask (task) {
// finalizes tasks
}
var taskCompleted = false;
async function doSomething (callback) {
const response = await fetch(some_url);
const task = await response.json();
if (task) {
taskCompleted = true;
}
if (typeof callback == "function" && taskCompleted) {
callback(task);
}
}
doSomething(endTask);
The purpose of the async/wait functionality is to reduce the complexity called Callback Hell. Async functions effectively removes the necessity of passing around callback functions.
However, one can still pass a callback function to an async function as a reference to be called later in his logic. This is perfectly alright as long as the developers do not go to the extent of wrapping Promises around async functions. That application goes against the concept of async/await.
Async functions always take parameters in real practice and some of them are callback functions which are to be called immediately or pass on to another function.
Shorthand version of your code:
function endTask (task) {
// finalizes tasks
}
async function doSomething () {
const response = await fetch(some_url);
const task = await response.json();
if (typeof callback == "function" && task) {
endTask (task);
}
}
doSomething();
I think if you already used async/await, maybe you don't need to use callback anymore. The purpose of async/await functions is to simplify the behavior of using promises, and avoid callback hell too.
Hope the example below could help.
function endTask (task) {
// finalizes tasks
}
async function doSomething (callback) {
const task = await fetch(some_url).then(res => res.json());
return task;
}
async function main() {
const task = await doSomething();
if (task) {
endTask()
}
}
Yes, async functions can take and call callbacks just like any other function, but that pretty much defeats the purpose of using an async function. There are some cases where there is a valid reason to do this (see Dave Newton's comment below), but this isn't one of them.
Instead, you should do something like this:
function endTask(task) {
// finalizes tasks
}
var taskCompleted = false;
async function doSomething() {
const response = await fetch(some_url);
const task = await response.json();
return task;
}
doSomething()
.then(task => {
if (task) {
taskCompleted = true;
}
endTask(task);
})
.catch(error => {
// handle error
});

How to get rid of async in function?

Let's say I have this code:
const myFunction = async => {
const result = await foobar()
}
const foobar = async () => {
const result = {}
result.foo = await foo()
result.bar = await bar()
return result
}
And I want this:
const myFunction = () => {
const result = foobar()
}
I tried to wrap foobar like this:
const foobar = async () => {
return (async () => {
const result = {}
result.foo = await foo()
result.bar = await bar()
return result
})()
}
But this still return a promise
I can't use .then in myFunction, I need that foobar returns the result variable instead a promise.
The problem is that myFunction is an async function and it will return a promise but It should return undefine I need to get rid of async in myFunction.
Edit: as Sebastian Speitel said, I want to convert myFunction to sync
Edit 2: to Shilly, I am using nightwatch for end2end test, nightwatch will call myFunction() if there are no errors in the execution of the function it will run perfectly, if there's an error then nightwatch's virtual machines will run forever instead stop, this problem happens if the called function is async.
To change an asynchronous function into a normal synchronous function you simply have to drop the async keyword and as a result all await keywords within that function.
const myFunction = async () => {
const result = await foobar();
// ...
return 'value';
};
// becomes
const myFunction = () => {
const result = foobar();
// ...
return 'value';
};
You should however keep one simple rule in mind.
You can't change a asynchronous function into a synchronous function if the return value depends on the value(s) of the resolved promise(s).
This means that functions that handle promises inside their body, but from whom the return value doesn't depend on those resolved promises are perfectly fine as synchronous functions. In most other scenarios you can't drop the asynchronous behaviour.
The following code gives you an example for your situation, assuming the return value of myFunction doesn't depend on the resolved promise.
const myFunction = () => {
const result = foobar();
result.then(data => doSomethingElse(data))
.catch(error => console.error(error));
return 'some value not dependent on the promise result';
};
If you want to learn more about promises I suggest checking out the promises guide and the async/await page.
Have you looked into using .executeAsync() and then having the promise call the .done() callback? That way it should be possible to wrap foobar and just keep either the async or any .then() calls inside that wrapper.
My nightwatch knowledge is very stale, but maybe something like:
() => {
client.executeAsync(( data, done ) => {
const result = await foobar();
done( result );
});
};
or:
() => {
client.executeAsync(( data, done ) => foobar().then( result => done( result )));
};
Any function marked with async will return a Promise. This:
const foobar = async () => {
return 7;
}
Will a return a Promise of 7. This is completely independent of wether the function that calls foobar is async or not, or uses await or not when calling it.
So, you're problem is not (only) with myFunction: is foobar using async which forces it to always return a Promise.
Now, said that, you probably can't achieve what you want. Async-Await is only syntax sugar for promises. What you're trying is to return a synchronous value from an asynchronous operation, and this is basically forbidden in javascript.
You're missing very important understanding here between the synchronous and asynchronous nature of the code.
Not every async function can be converted to synchronous function. You can use callback pattern instead of await/async, but I doubt that would be useful to you.
Instead I would recommend you just use await, as in your first code example, and leave functions as async, it shouldn't harm your logic.
Check this out
function foo(){
return 'foo'
}
function bar(){
return 'bar'
}
const foobar = () => {
return new Promise((resolve)=>{
let result = {}
result.foo = foo()
result.bar = bar()
return resolve(result)
})
}
const myFunction = () => {
const result = foobar()
let response = {}
result.then(val=>{
response = Object.assign({}, val);
return response
});
}
var test = myFunction()
console.log(test)

'await' is not working in async function

The code below gives me the following error:
SyntaxError: await is only valid in async function
async function getLastTransaction()
{
paymentsApi.listPayments(locationId, opts).then(function(transactions)
{
if(transactions[transactions.length-1] === undefined)
return; //no new transaction yet
var last_transaction_id = transactions[transactions.length-1].id;
var last_transaction_in_queue;
try {
last_transaction_in_queue = JSON.parse(order_queue[0]).order_id;
} catch (e) {
last_transaction_in_queue = order_queue[0].order_id;
}
//check if latest transaction is the same as lastest transaction in queue
if(last_transaction_id !== last_transaction_in_queue) {
console.log(`new payment...`);
var obj = await createTransactionObject(transactions[transactions.length-1], () => {
order_queue.unshift(obj);
console.log('new added', order_queue);
});
}
I don't understand the error since I'm using await for the same function createTransactionObject() but in another piece of code.
For example, in the following code, I don't get the error, and still, I'm using await before createTransactionObject()
async function populateQueue(transaction_list) {
for(var i = 0; i < transaction_list.length; i++)
{
var transaction_json = await createTransactionObject(transaction_list[i], () => {});
order_queue.unshift(transaction_json);
} }
You need to change this line:
paymentsApi.listPayments(locationId, opts).then(function(transactions)
to this:
paymentsApi.listPayments(locationId, opts).then(async (transactions) =>
The anonymous function you supply to .then needs to be asynced, because you're using await in it.
You could also replace the line with this (maybe even better):
const transactions = await paymentsApi.listPayments(locationId, opts);
Because the getLastTransaction function is asynced.
First of all you get the error not because the getLastTransaction function is async but because the anonymous function .then(function(transactions) is not async and you use await inside of it. You can't do that.
Now note that simple redeclaring the function as async function(transactions) will be syntactically correct but will that work fine? What happens now is that getLastTransaction fires some async process in the background and never awaits the result. Is that what you want?
To fix that you have to ask yourself: what am I trying to achieve? Should getLastTransaction wait for whatever the inner function is doing? Then make use of that async declaration:
async function getLastTransaction() {
const transactions = await paymentsApi.listPayments(locationId, opts);
// Some other code here
return xyz;
}
This is under the assumption that the paymentsApi is async/await compatible. If it is not then you have to play with manually creating and returning Promise objects (in which case async declaration won't help much).
paymentsApi.listPayments(locationId, opts).then(function(transactions) should be
paymentsApi.listPayments(locationId, opts).then(async function(transactions) as await can only be used in an asynchronous function.
Better still, since you already have an async function at the top level, why don't you just await paymentsApi.listPayments(locationId, opts) instead of chaining it with a then?
async function getLastTransaction() {
const transactions = await paymentsApi.listPayments(locationId, opts);
// Do something with transactions
}
await keyword works when scope is having async keyword used, here .then accepts callback function that doesn't have async, so await becomes alien here.
Lets re-write your code in async-await style:
async function getLastTransaction()
{
// #1 this fixes to adopt the await style and fixes the problem
const transactions = await paymentsApi.listPayments(locationId, opts);
// your rest code goes here ...
if(last_transaction_id !== last_transaction_in_queue) {
//now do it like this, await will make sense now
const obj = await createTransactionObject(transactions[transactions.length-1]);
order_queue.unshift(obj);
}
}

ES8 Immediately invoked async function expression

I haven't seen these constructs used much but I've found myself writing them to make use of async / await in functions that wouldn't typically return a promise, for example
chan.consume(queue, (msg) => {
this.pendingMsgs++; // executed immediately
(async () => {
await this.handleMessage(msg);
this.pendingMsgs--;
if (cancelled && this.pendingMsgs === 0) {
await chan.close();
await this.amqpConnectionPool.release(conn);
}
})();
});
as opposed to
chan.consume(queue, async (msg) => { // external lib does not expect a return value from this callback
this.pendingMsgs++; // executed in promise context(?)
await this.handleMessage(msg);
this.pendingMsgs--;
if (cancelled && this.pendingMsgs === 0) {
await chan.close();
await this.amqpConnectionPool.release(conn);
}
});
or
chan.consume(queue, (msg) => {
this.pendingMsgs++; // no await - excess function decls & nesting
this.handleMessage(msg).then(() => {
this.pendingMsgs--;
if (cancelled && this.pendingMsgs === 0) {
chan.close().then(() => {
this.amqpConnectionPool.release(conn);
});
}
});
});
Is this 'a thing'? Are there pitfalls here I should be aware of?
What's the lowdown on use of async / await in these kind of situations?
Is this 'a thing'?
Yes. It comes up every now and then, e.g. here. They're known as IIAFEs :-)
If you want to put focus on the arrow, you could also call them IIAAFs.
Are there pitfalls here I should be aware of?
Whenever you call a promise-returning function and don't return the result to somewhere else, you are responsible for the promise yourself - which means that you have to handle errors from it. So the pattern should in general look like
(async () => {
…
})().catch(err => {
console.error(err);
});
if you don't want to concern yourself with unhandled-rejection events.
What's the lowdown on use of async/await in these kind of situations?
Not much, compared to the then version. However, you say "the external lib does not expect a return value from this callback", which might hint at the library's incompatibility with asynchronous callbacks, so beware what you are doing when. It also might depend on exceptions being thrown synchronously from the callback, so it all depends on what the library expects here (and if there are no expectations, whether that may change in the future). You don't want future incompatibilities in case the library will start to treat promise return values specially.
However, I would still recommend the second pattern that directly passes the async function directly as the callback because of its better readability. If you want to avoid returning a promise to the library, create a helper function that wraps the callback:
function toVoid(fn) {
return (...args) => void fn(...args);
}
function promiseToVoid(fn) {
return (...args) => void fn(...args).catch(console.error);
}
which you could use like this:
chan.consume(queue, toVoid(async (msg) => {
… // use `await` freely
}));
(async () => {
await func();
})();

Categories

Resources