All async/await code can be translated to Promises, or other constructs. Because this is what transpilation with babel has done.
I has assumed the two paradigms where equivalent and that all promises could be rewritten with async/await. Is this true? Or is it an assumption I need to drop.
For a concrete example I have the following code, which contains a promise.
I have not seen a way to translate this code to async/await only.
For context, this Mailbox code is for a demo I have to explain the Actor model in the context of the browser/JavaScript
function Mailbox () {
const messages = []
var awaiting = undefined
this.receive = () => {
if (awaiting) { throw 'Mailbox alread has a receiver'}
return new Promise((resolve) => {
if (next = messages.shift()) {
resolve(next)
} else {
awaiting = resolve
}
})
}
this.deliver = async (message) => {
messages.push(message)
if (awaiting) {
awaiting(messages.shift())
awaiting = undefined
}
}
}
async/await use promises. In fact, they don't do anything useful if you don't have a promise to await. They don't replace promises. You would typically use await instead of .then() on a promise.
Let's look at a simple example. Suppose you have two functions that return promises.
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
const rp = require('request-promise');
function getData(uri) {
let options = {uri, json: true};
return rp(options);
}
Now, you want to get data from three different URLs with a 1 second delay between the requests.
Regular Promises
With regular promises (no async/await), you could do something like this using promise chaining:
function getAllData() {
let result = {};
return getData(firstURL).then(data => {
results.d1 = data;
return delay(1000);
}).then(() => {
return getData(secondURL);
}).then(data => {
results.d2 = data;
return delay(1000);
}).then(() => {
return getData(thirdURL);
}).then(data => {
results.d3 = data;
return results;
});
}
getAllData().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
Using Async/Await
Using await, you can simplify the sequencing of multiple asynchronous operations, but those asynchronous operations STILL use promises. You are just replacing some of the chained .then() operations with await.
async function getAllData() {
let result = {};
result.d1 = await getData(firstURL);
await delay(1000);
result.d2 = await getData(secondURL);
await delay(1000);
result.d3 = await getData(thirdURL);
await delay(1000);
return result;
}
getAllData().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
Related
I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.
I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.
I have a Firebase Function which sends back data from databases. The problem is sometimes I have to return data all of 3 collections, sometimes only need from 1 and sometimes 2 of them. But this is an antipattern. How can I improve my code?
Right now I'm creating a function, which returns a promise, in which I'm using await for getting db values and this is wrapped in try{} block.
module.exports.getList = (uid, listType) => new Promise(async (resolve, reject) => {
let returnValue = [];
try {
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
} catch (e) {
return reject(e);
}
return resolve(returnValue);});
How should I modify this snippet in order to not be an antipattern? Or is it not because of the try-catch block?
You can make the getList function async instead, without new Promise or try/catch:
module.exports.getList = async (uid, listType) => {
const returnValue = [];
if (listType.contains("a")) {
const block = await db.collection('alist').doc(uid).get();
returnValue.push(block);
}
if (listType.contains("b")) {
const like = await db.collection('blist').doc(uid).get();
returnValue.push(like);
}
if (listType.contains("c")) {
const match = await db.collection('clist').doc(uid).get();
returnValue.push(match);
}
return returnValue;
};
Calling it will return a Promise that rejects with an error if there's an asynchronous error, or it will resolve to the desired array.
Note that unless there's a good reason to await each call in serial, you can use Promise.all instead, so that the requests go out in parallel, and make the code a lot more concise in the process:
module.exports.getList = (uid, listType) => Promise.all(
['alist', 'blist', 'clist']
.filter(name => listType.contains(name[0]))
.map(name => db.collection(name).doc(uid).get())
);
I am on Node 8 with Sequelize.js
Gtting the following error when trying to use await.
SyntaxError: await is only valid in async function
Code:
async function addEvent(req, callback) {
var db = req.app.get('db');
var event = req.body.event
db.App.findOne({
where: {
owner_id: req.user_id,
}
}).then((app) => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 6000)
})
// I get an error at this point
let result = await promise;
// let result = await promise;
// ^^^^^
// SyntaxError: await is only valid in async function
}
})
}
Getting the following error:
let result = await promise;
^^^^^
SyntaxError: await is only valid in async function
What am I doing wrong?
You can run await statement only under async function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
So, you can write your
}).then((app) => {
as
}).then(async (app) => {
addEvent is a mixture of async..await and raw promises. await is syntactic sugar for then. It's either one or another. A mixture results in incorrect control flow; db.App.findOne(...).then(...) promise is not chained or returned and thus is not available from outside addEvent.
It should be:
async function addEvent(req, callback) {
var db = req.app.get('db');
var event = req.body.event
const app = await db.App.findOne({
where: {
owner_id: req.user_id,
}
});
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 6000)
})
let result = await promise;
}
Generally plain callbacks shouldn't be mixed with promises. callback parameter indicates that API that uses addEvent may need to be promisified as well.
async/await only works if the immediate function has the async keyword, you need this:
...
}).then(async app => { // <<<< here
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 6000)
})
// I get an error at this point
let result = await promise;
// let result = await promise;
// ^^^^^
// SyntaxError: await is only valid in async function
}
})
You can use await only inside a function which is async. Also you can await only a piece of code that returns a promise.
Here you are using await inside a different context. Better you use then() here to solve the problem.
await only works if the immediate function that encloses it is async.
I have the following functions with promises:
const ajaxRequest = (url) => {
return new Promise(function(resolve, reject) {
axios.get(url)
.then((response) => {
//console.log(response);
resolve(response);
})
.catch((error) => {
//console.log(error);
reject();
});
});
}
const xmlParser = (xml) => {
let { data } = xml;
return new Promise(function(resolve, reject) {
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data,"text/xml");
if (xmlDoc.getElementsByTagName("AdTitle").length > 0) {
let string = xmlDoc.getElementsByTagName("AdTitle")[0].childNodes[0].nodeValue;
resolve(string);
} else {
reject();
}
});
}
I'm trying to apply those functions for each object in array of JSON:
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
I came up with the following solution:
function example() {
_.forEach(array, function(value) {
ajaxRequest(value.url)
.then(response => {
xmlParser(response)
.catch(err => {
console.log(err);
});
});
}
}
I was wondering if this solution is acceptable regarding 2 things:
Is it a good practice to apply forEach() on promises in the following matter.
Are there any better ways to pass previous promise results as parameter in then() chain? (I'm passing response param).
You can use .reduce() to access previous Promise.
function example() {
return array.reduce((promise, value) =>
// `prev` is either initial `Promise` value or previous `Promise` value
promise.then(prev =>
ajaxRequest(value.url).then(response => xmlParser(response))
)
, Promise.resolve())
}
// though note, no value is passed to `reject()` at `Promise` constructor calls
example().catch(err => console.log(err));
Note, Promise constructor is not necessary at ajaxRequest function.
const ajaxRequest = (url) =>
axios.get(url)
.then((response) => {
//console.log(response);
return response;
})
.catch((error) => {
//console.log(error);
});
The only issue with the code you provided is that result from xmlParser is lost, forEach loop just iterates but does not store results. To keep results you will need to use Array.map which will get Promise as a result, and then Promise.all to wait and get all results into array.
I suggest to use async/await from ES2017 which simplifies dealing with promises. Since provided code already using arrow functions, which would require transpiling for older browsers compatibility, you can add transpiling plugin to support ES2017.
In this case your code would be like:
function example() {
return Promise.all([
array.map(async (value) => {
try {
const response = await ajaxRequest(value.url);
return xmlParser(response);
} catch(err) {
console.error(err);
}
})
])
}
Above code will run all requests in parallel and return results when all requests finish. You may also want to fire and process requests one by one, this will also provide access to previous promise result if that was your question:
async function example(processResult) {
for(value of array) {
let result;
try {
// here result has value from previous parsed ajaxRequest.
const response = await ajaxRequest(value.url);
result = await xmlParser(response);
await processResult(result);
} catch(err) {
console.error(err);
}
}
}
Another solution is using Promise.all for doing this, i think is a better solution than looping arround the ajax requests.
const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]
function example() {
return Promise.all(array.map(x => ajaxRequest(x.url)))
.then(results => {
return Promise.all(results.map(data => xmlParser(data)));
});
}
example().then(parsed => {
console.log(parsed); // will be an array of xmlParsed elements
});
Are there any better ways to pass previous promise results as
parameter in then() chain?
In fact, you can chain and resolve promises in any order and any place of code. One general rule - any chained promise with then or catch branch is just new promise, which should be chained later.
But there are no limitations. With using loops, most common solution is reduce left-side foldl, but you also can use simple let-variable with reassign with new promise.
For example, you can even design delayed promises chain:
function delayedChain() {
let resolver = null
let flow = new Promise(resolve => (resolver = resolve));
for(let i=0; i<100500; i++) {
flow = flow.then(() => {
// some loop action
})
}
return () => {
resolver();
return flow;
}
}
(delayedChain())().then((result) => {
console.log(result)
})