How do I use async and await in protractor tests?
it('test async', function(){
var value = 0;
function asyncAction() {
return browser.driver.wait(()=>true)
.then(function () {
console.log('a');
return value++;
});
}
//-Problem Area-
async function doAwait(){
await asyncAction();
return asyncAction();
}
doAwait();
protractor.promise.controlFlow().execute( () => {
console.log('b');
expect(value).toBe(2);
});
});
output here is
a
b
a
and value is 1 at time of expect
function doAwait(){
await asyncAction();
return asyncAction();
}
I like to think of this as similar to
function doAwait(){
asyncAction().then(()=>asyncAction());
}
Which works but the above async doAwait does not. I believe this is because the generator breaks the ControlFlow of selenium.
Adding this to the protractor configuration works:
var webdriver = require.main.require('selenium-webdriver');
Promise = webdriver.promise.Promise;
Object.assign(Promise, webdriver.promise);
Promise.resolve = Promise.fulfilled;
Promise.reject = Promise.rejected;
Though maybe not all promises are supposed to be managed promises?
Worth noting that the other solution requires wrapping each async function:
protractor.promise.controlFlow().execute( async () => {
await asyncAction();
return asyncAction();
});
See https://github.com/angular/jasminewd#async-functions--await:
async functions / await
async functions and the await keyword are likely coming in ES2017 (ES8), and available via several compilers. At the moment, they often break the WebDriver control flow. (GitHub issue). You can still use them, but if you do then you will have to use await/Promises for almost all your synchronization. See spec/asyncAwaitAdapterSpec.ts and spec/asyncAwaitErrorSpec.ts for examples.
Related
Is it possible to run an asynchronous function without making the main function as async?
function index() {
console.log('1');
aaa();
console.log('3');
}
async function aaa() {
return await bbb().then((value) => {
console.log(value);
});
}
async function bbb() {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2');
}, 1000);
});
}
index();
It is possible to display:
1
2
3
Without making the index function as async?
You need to do something to cause console.log('3') to run after the promise returned by aaa() has been resolved.
Making index async and using await is the approach that most people find easiest to write and maintain.
async function index() {
console.log('1');
await aaa();
console.log('3');
}
You could use then() instead.
function index() {
console.log('1');
aaa().then(
() => { console.log('3'); }
);
}
Since aaa returns a Promise, you can always interact directly with that Promise instead of using await. For example:
function index() {
console.log('1');
aaa().then(() => console.log('3'));
}
That way the code in the .then() callback won't execute until after the Promise returned by aaa() resolves.
For the most part, without peeking under the hood to the inner workings of JavaScript, async and await tends to just be a cleaner and simpler way of expressing this same thing. For this simple example, there really shouldn't be a meaningful difference between this:
function index() {
console.log('1');
return aaa().then(() => console.log('3'));
}
And this:
async function index() {
console.log('1');
await aaa();
console.log('3');
}
The main difference between these and the function at the top of this answer is that the one at the top doesn't advertise that it internally has asynchronous operations. So it can't be awaited. If that's not an issue for your needs, no big deal. But even then, going with async and await still does the same thing and consuming code can simply choose not to await this function.
I'd like to accomplish the following using promises: only execute further once the state of something is ready. I.e. like polling for an external state-change.
I've tried using promises and async-await but am not getting the desired outcome. What am I doing wrong here, and how do I fix it?
The MDN docs have something similar but their settimeout is called within the promise--that's not exactly what I'm looking for though.
I expect the console.log to show "This function is now good to go!" after 5 seconds, but instead execution seems to stop after calling await promiseForState();
var state = false;
function stateReady (){
state = true;
}
function promiseForState(){
var msg = "good to go!";
var promise = new Promise(function (resolve,reject){
if (state){
resolve(msg);
}
});
return promise;
}
async function waiting (intro){
var result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
What you're doing wrong is the promise constructor executor function executes immediately when the promise is created, and then never again. At that point, state is false, so nothing happens.
Promises (and async/await) are not a replacement for polling. You still need to poll somewhere.
The good news: async functions make it easy to do conditional code with loops and promises.
But don't put code inside promise constructor executor functions, because of their poor error handling characteristics. They are meant to wrap legacy code.
Instead, try this:
var state = false;
function stateReady() {
state = true;
}
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function promiseForState() {
while (!state) {
await wait(1000);
}
return "good to go!";
}
async function waiting(intro) {
var result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
Based on your comments that you are waiting for messages from a server it appears you are trying to solve an X/Y problem. I am therefore going to answer the question of "how do I wait for server messages" instead of waiting for global variable to change.
If your network API accepts a callback
Plenty of networking API such as XMLHttpRequest and node's Http.request() are callback based. If the API you are using is callback or event based then you can do something like this:
function myFunctionToFetchFromServer () {
// example is jQuery's ajax but it can easily be replaced with other API
return new Promise(function (resolve, reject) {
$.ajax('http://some.server/somewhere', {
success: resolve,
error: reject
});
});
}
async function waiting (intro){
var result = await myFunctionToFetchFromServer();
console.log(intro + result);
}
If your network API is promise based
If on the other hand you are using a more modern promise based networking API such as fetch() you can simply await the promise:
function myFunctionToFetchFromServer () {
return fetch('http://some.server/somewhere');
}
async function waiting (intro){
var result = await myFunctionToFetchFromServer();
console.log(intro + result);
}
Decoupling network access from your event handler
Note that the following are only my opinion but it is also the normal standard practice in the javascript community:
In either case above, once you have a promise it is possible to decouple your network API form your waiting() event handler. You just need to save the promise somewhere else. Evert's answer shows one way you can do this.
However, in my not-so-humble opinion, you should not do this. In projects of significant size this leads to difficulty in tracing the source of where the state change comes form. This is what we did in the 90s and early 2000s with javascript. We had a lot of events in our code like onChange and onReady or onData instead of callbacks passed as function parameters. The result was that sometimes it takes you a long time to figure out what code is triggering what event.
Callback parameters and promises forces the event generator to be in the same place in the code as the event consumer:
let this_variable_consumes_result_of_a_promise = await generate_a_promise();
this_function_generate_async_event((consume_async_result) => { /* ... */ });
From the wording of your question you seem to be wanting to do this instead;
..somewhere in your code:
this_function_generate_async_event(() => { set_global_state() });
..somewhere else in your code:
let this_variable_consumes_result_of_a_promise = await global_state();
I would consider this an anti-pattern.
Calling asynchronous functions in class constructors
This is not only an anti-pattern but an impossibility (as you've no doubt discovered when you find that you cannot return the asynchronous result).
There are however design patterns that can work around this. The following is an example of exposing a database connection that is created asynchronously:
class MyClass {
constructor () {
// constructor logic
}
db () {
if (this.connection) {
return Promise.resolve(this.connection);
}
else {
return new Promise (function (resolve, reject) {
createDbConnection(function (error, conn) {
if (error) {
reject(error);
}
else {
this.connection = conn; // cache the connection
resolve(this.connection);
}
});
});
}
}
}
Usage:
const myObj = new MyClass();
async function waiting (intro){
const db = await myObj.db();
db.doSomething(); // you can now use the database connection.
}
You can read more about asynchronous constructors from my answer to this other question: Async/Await Class Constructor
The way I would solve this, is as follows. I am not 100% certain this solves your problem, but the assumption here is that you have control over stateReady().
let state = false;
let stateResolver;
const statePromise = new Promise( (res, rej) => {
stateResolver = res;
});
function stateReady(){
state = true;
stateResolver();
}
async function promiseForState(){
await stateResolver();
const msg = "good to go!";
return msg;
}
async function waiting (intro){
const result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
Some key points:
The way this is written currently is that the 'state' can only transition to true once. If you want to allow this to be fired many times, some of those const will need to be let and the promise needs to be re-created.
I created the promise once, globally and always return the same one because it's really just one event that every caller subscribes to.
I needed a stateResolver variable to lift the res argument out of the promise constructor into the global scope.
Here is an alternative using .requestAnimationFrame().
It provides a clean interface that is simple to understand.
var serverStuffComplete = false
// mock the server delay of 5 seconds
setTimeout(()=>serverStuffComplete = true, 5000);
// continue until serverStuffComplete is true
function waitForServer(now) {
if (serverStuffComplete) {
doSomethingElse();
} else {
// place this request on the next tick
requestAnimationFrame(waitForServer);
}
}
console.log("Waiting for server...");
// starts the process off
requestAnimationFrame(waitForServer);
//resolve the promise or whatever
function doSomethingElse() {
console.log('Done baby!');
}
I have some nodejs scripts - i.e. processes which do a job and complete, rather than run continuously.
I use async functions, for example:
const mysql = require('mysql2/promise');
...
async function main() {
var conn = await mysql.createConnection(config.mysql);
...
var [response, error] = await conn.execute(`
DELETE something
FROM some_table
WHERE field = ?
`, [value]);
...
Is the following code:
main().then(() => process.exit(0)).catch(err => { console.error(err); process.exit(1); });
the best/correct way to start execution of the async code?
(It works, but I want to make sure that I'm not leaving any holes which might cause surprises, such as exceptions being silently swallowed.)
Why does conn.execute() return an error (which I need to manually check) rather than throwing one?
The use of then together with async..await isn't necessary because it's syntactic sugar for then.
Entry point could be async IIFE (IIAFE):
(async () => {
try {
var conn = await mysql.createConnection(config.mysql);
...
var [response] = await conn.execute(`
SELECT something
FROM some_table
WHERE field = ?
`, [value]);
...
process.exit(0);
} catch (err) {
console.error(err);
process.exit(1);
}
})();
There also may be no need for process.exit(0) if the connection was closed.
Why does conn.execute() return an error (which I need to manually check) rather than throwing one?
It doesn't, and isn't conventional for promise-enabled functions to return an error in a result.
Callback-based execute uses error-first callback for errors. Promise-based execute can't throw an error because it returns a promise which is rejected in case of error.
As the documentation shows, the second element is fields and not error:
const [rows, fields] = await conn.execute('select ?+? as sum', [2, 2]);
It may return rejected promise which can be caught with try..catch inside async function in case of error.
The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.
You can also define async functions using an async function expression.
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
you can also immediately call an asynce function using the syntax below
(async () => {
})();
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);
}
}
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();
})();