Take a look at this sample JavaScript code that uses Promises:
const promiseGen = () => {
return new Promise((resolve, reject) => {
reject('something')
})
}
promiseGen().catch(err => console.log(err))
console.log('after promiseGen')
I would have expected the output to be:
something
after promiseGen
But instead, it is
after promiseGen
something
Can someone explain why this is happening? Since I'm creating the promise first, which in turn calls reject, shouldn't something be printed first? Is the promise run on a separate thread such that the order of operations is not guaranteed?
The Promise is always async (even if ou resolve it immediately), and will therefore always resolve after the sync code.
The specification of Promises require that even if they are doing nothing async they should behave in async manner for uniformity. So here, the then/catch method of the promise will get called on the next event loop check, which will happen when the call stack gets empty, which will become empty only after the execution of the last console.log() statement. Hence the results.
Related
I got a piece of code that would submit several set of reports which they are independent each other, currently wrote in promise.allSettled, but I was told that the team standard would require async await instead of promise
"Never use multiple await for two or more independent async parallel tasks, because you will not be able to handle errors correctly. Always use Promise.all() for this use case."
1
"In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other / if you'd like to immediately reject upon any of them rejecting."
2
"Using for await...of, you have more granular control of the promises. So if the order in which promises complete is important to you, for await...of is your preferred choice. However, the increased control isn’t free. The fact that for await...of handles promises one by one, makes it a lot slower."
"To sum up, the three methods are all capable of handling iterables of promises, but differ slightly in their functioning. Use for await of if the order in which promises are resolved is important to you. Use Promise.all() if the order isn’t important and you need all calls to succeed. Use Promise.allSettled() if the order isn’t important and you don’t absolutely need all individual calls to be successful."
3
After some research, I found it is not possible to rewrite it in async await with the same efficiency (request execute in parallel) and simplicity (promise.allSettled is a built-in function), am I correct?
That piece of code
const recordInsertErrors:Object[] = [];
await Promise.allSettled(
jsonArray.map((eachPositionReport) => {
return PositionReport.query().insert(eachPositionReport).catch((err) => {
const error = { vessel_ownership_id: eachPositionReport.vessel_ownership_id, error: err.nativeError };
recordInsertErrors.push(error);
throw err;
});
}),
);
First of all JavaScript code does not run in parallel. The most we can say is that it executes asynchronously, i.e. it gets executed by the engine while monitoring its job queues. The "only" thing that might execute in parallel is lower-level, non-JavaScript logic, such as provided by some APIs that make asynchronous HTTP requests.
Secondly, whether an asynchronous operation starts while another is still underway, is not determined by the use of Promise.all, Promise.allSettled, for await ... of, ...etc, but by whether or not all involved promises are created immediately or not. That is part of the code that is not orchestrated by any of the mentioned constructs.
So surely you can use async and await keywords to achieve that asynchronous requests are made without waiting that a previous one has completed.
For instance:
const recordInsertErrors = [];
const promises = jsonArray.map(async (eachPositionReport) => {
let value;
try {
value = await PositionReport.query().insert(eachPositionReport);
} catch(err) {
value = {
vessel_ownership_id: eachPositionReport.vessel_ownership_id,
error: err.nativeError
};
}
return value;
});
// All promises will now fulfill, as errors are converted to
// fulfillments with an error property
(async () => {
for (const promise of promises) {
const value = await promise;
if (value.error) recordInsertErrors.push(value);
console.log(value);
}
})();
The for loop with await expressions will not delay the moment at which all promises have resolved. It will potentially report sooner on some results than Promise.allSettled, as the latter is designed to first wait until all promises have settled, and only then resolve its own promise.
I have been using Promises and async/await, they are pretty much the same thing right? Usually what I would do is wrap my promise and return it etc.
function someFetchThatTakesTime(){
// Promisify the request.
return new Promise((resolve, reject) => {
if(allGood){
resolve();
}else{
reject();
});
}
Then I can do:
someFetchThatTakesTime()
.then(console.log('all good.')
.catch(console.log('some error occured.');
or I can do the:
async function wrapMyFetch() {
try {
// Make the async call.
data = await someFetchThatTakesTime();
return data;
} catch(err) {
// Propagate the exception up stream.
throw err;
}
}
(async () => {
let response = await wrapMyFetch();
// Do stuff with the response.
})();
I guess, pretty clear so far.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
I would like to clarify this: async/await will just force things to run linearly, right? But if we do not want linearity, and we are ok with spawned tasks - Promises - to finish their thing in the background in some time when we are resuming with our run-cycle, we don't need that async/await pattern? Is that correct?
I have been using Promises and async/await, they are pretty much the same thing right?
Well, async/await are helpful syntax built on top of promises. Async/await relies on promises in order to work. So, I wouldn't quite call them the same thing, but you can code identically functioning code with await surrounded by try/catch and .then().catch(). You can use either. Both methods rely on promises.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
There is no rule that the caller has to pay attention to a returned promise or has to wait for it before going on with their task. So, if you want to "just let the stones fall as they may in the background" as you say and that's appropriate for your application, you can code that way.
This is often referred to as "fire and forget" coding where you start some asynchronous operation that is going to do something on its own and you don't need to pay attention to when it finishes or if it had an error because that is of no consequence to the calling code.
But, there is a rule that you can't let a promise rejection go unhandled. So, if you're returning a promise and the caller isn't going to do anything with it, then you have to make sure that any rejections are handled by the operation itself (with .catch()) so that the only thing that ever gets returned back is a resolved promise. Even if all you do is notice the error and do nothing with it, you have to catch the error.
async/await will just force things to run linearly, right?
Not globally, no. await makes the code in the async function it's used in wait for the promise you pass it to settle, but that only affects the function you use it in, not anything calling that function.
async functions are syntactic sugar for writing a function that returns a promise. await is syntactic sugar for consuming promises. Together, they dramatically simplify using promises (in particular by making rejections errors that automatically propagate through the call tree).
More specifically, an async function runs its code synchronously until the first await or return (or until code runs off the end of the function), at which point it returns a promise. Its logic then waits for the promise to settle, and continues; eventually it settles its promise based on what happened to the last promise it awaited or returned (or fulfills it with undefined if code execution runs off the end).
If you don't want to wait for a promise to settle, you don't have to, not even in an async function. Just don't use await on it. For instance, assume we have this wrapper for fetch that gets JSON for us (and fixes the API footgun):
async function fetchJSON(...args) {
const response = await fetch(...args);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
}
Here's an async function using it that does an initial query to get a list of things to fetch, and then fetches all of those things in parallel::
async function fetchList(listUrl) {
const list = await fetchJSON(listUrl);
return Promise.all(list.map(item => fetchJSON(itemUrl)));
}
Notice how fetchList uses await to wait for the list of things to fetch, but then doesn't wait for the items; it just returns the promise from Promise.all for the list of items it starts fetching.
Also note how await within fetchJSON makes fetchJSON's logic wait for the fetch promise to settle, but doesn't make the caller calling fetchJSON wait unless that caller uses await. fetchList only awaits the first call (to get the list), it doesn't wait for the item from the list.
I have abstraction:
function fetchDataFromAPI() {
const url = `https://api...`
return fetch(url).then(response => response.json())
}
I want to use it in my other piece of code like:
if(something){
const data = fetchDataFromAPI()
return data
}
if I console.log data what I get is resolved pending promise
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
How do I get that Object in data instead of Promise?
You can not. Here is why:
Promise is a language construct that makes JavaScript engine to continue to execute the code without waiting the return of inner function, also known as the executor function. A promise always run inside the event loop.
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
console.log(p);
Basically a promise is a glorified syntactic sugar for a callback. We will see how but first lets have a more realistic code:
function someApiCall(){
return new Promise(function(resolve, reject){
setTimeout(()=>{
resolve('Hello');
})
})
}
let data = someApiCall();
console.log(data);
This is a so-called asynchronous code, when JavaScript engine executes it, someApiCall immediately returns a result, in this case pending promise:
> Promise {<pending>}
If you pay attention to the executor, you will see we needed to pass resolve and reject arguments aka callbacks. Yes, they are callbacks required by the language construct. When either of them called, promise will change its state and hence be settled. We don't call it resolved because resolving implies successful execution but a function also can error out.
How do we get the data? Well we need more callbacks, which will be called by the executor function once the promise is settled:
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
p.then((result) => {
console.log(result); // foo
}).catch((err) => {
console.log(err);
});
Why we need to pass separate callbacks? Because one will be fed to the resolve, and the other to the reject. Then callback will be called by the resolve function, the catch callback by the reject function.
Javascript engine will execute these callbacks later on its leisure, for a regular function it means when the event loop is cleared, for timeout when the time is up.
Now to answer your question, how do we get data out from a promise. Well we can't.
If you look closely, you will see we don't really get the data out but keep feeding callbacks. There is no getting data out, but passing callbacks in.
p.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
});
Some say use await:
async function() {
let result = await p;
}
But there is a catch. We have to or wrap it in async function. Always. Why? Because Async/await is another level of abstraction or syntactic sugar, whichever you prefer, on top of promise api. That is why we can not use await directly but always wrap it in async statement.
To sum up, when we use promise or async/await we need to follow certain convention and write terse code with closely knitted callbacks. Either javascript engine or transpilers like babeljs or typescript converts these code to regular javascript to be run.
I can understand your confusion because people keep saying getting data out when talking about promises, but we don't get any data out but pass callback to be executed when the data is ready.
Hope everything is clear now.
No, you cannot without using promises or async/await etc because calling a REST API is an asynchronous operation and is non blocking.
When you make a call to a REST API, the code shouldn't wait until the API returns a value because it may take a lot of time, making program non-responsive, thus by design making a network request is categorized as an asynchronous operation.
To avoid async/await, you'll need to use another .then:
if (something) {
return fetchDataFromAPI()
.then((data) => data /* you can console.log(data) here */)
}
I have a function that writes data to a mongodb, like so:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
dataKeys.forEach(async key => db.collection(key).insertMany(data[key]))
}
This works fine if I run it in a node script. But when I tried to use it in Jest's beforeAll I got this async error from Jest:
Jest did not exit one second after the test run has completed. This
usually means that there are asynchronous operations that weren't
stopped in your tests.
after some troubleshooting I found out that forEach was causing the trouble. Using a for loop solved this problem:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
for (const key of dataKeys) {
await db.collection(key).insertMany(data[key])
}
}
Searching for this problem I came across this article:
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
The explanation there made sense, but it left me with some questions:
According to the article, it should not have worked even in the node
script. How come it did?
Why is executing it in node different from running it in Jest?
edit
After reading all the comments, I realise my first question was a bit nonsense. Normally I assign the result of an async function to a variable, and if I don't put await, there will an undefined error down the line. But that's not the case here, so script exits normally and the db writes happen concurrently in the background.
The existing answer already explains in detail why forEach shouldn't be used with promises the way it's used. forEach callback doesn't take returned promises into account and breaks promise chain. async..await needs to be used with for..of to evaluate promises in series or with Promise.all and map to evaluate in parallel.
Jest supports promises and expects that a promise that is returned from asynchronous function (it, etc) signifies that asynchronous process that occurred in this function has ended.
Once Jest finishes a test, it checks if there are open handles that prevent Node from exiting. Since promises weren't returned and chained by Jest, processes that they represent prevent Jest from finishing test process.
This problem is represented by said error message:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with --detectOpenHandles
to troubleshoot this issue.
Async functions work even in contexts that don't await or call .then() on them, that is, I can definitely do this:
async function foo() {
// async calls here
}
foo(); // no await or .then().
This means you cannot wait for the operation to finish, you can't use the value, and worst of all, you cannot catch or recover from any async errors that might be thrown (or rejected, if we're being accurate)
The main difference is that the .forEach() doesn't care or wait for the operations to finish before calling the next one (since async functions return immediately), whereas your for..of call uses await to wait for each operation to complete before moving on to the next.
Your first .forEach example would be roughly equivalent to the bottom one if you removed the await from the call inside of the loop.
The result is that your first example returns a Promise that resolves immediately, rather than after all of your DB calls were finished, so the test expects the operations to finish, but they are not. In the second example, you properly await for all the calls to finish before the async function finishes, so the return Promise will only resolve after all of the calls resolve themselves, first.
On that note, the two examples are not equivalent because the first would call the insertMany one after the other without waiting for them to finish, causing the DB calls to perform in parallel.
If you want to preserve this behavior, but still return a correct Promise which waits for everything to finish, you should be using [].map() instead of [].forEach:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
const allPromises = dataKeys.map(async key =>
await db.collection(key).insertMany(data[key])) // run all calls in parallel
return await Promise.all(allPromises); // wait for them all to finish
}
I see there is an eslint rule, no-return-await, for disallowing return await.
In the rule's description, it states a return await adds "extra time before the overarching Promise resolves or rejects".
However, when I look at MDN async function docs, the "Simple Example" shows an example containing return await without any description of why this might be a performance problem.
Is return await an actual performance problem as the eslint docs suggest?
And if so, how?
No, there isn't any performance problem. It's just an unnecessary extra operation. It might take a bit longer to execute, but should be hardly noticeable. It's akin to return x+0 instead of return x for an integer x. Or rather, exactly equivalent to the pointless .then(x => x).
It doesn't do actual harm, but I'd consider it bad style and a sign that the author does not fully comprehend promises and async/await.
However, there's one case where it make an important difference:
try {
…
return await …;
} …
await does throw on rejections, and in any case awaits the promise resolution before catch or finally handlers are executed. A plain return would have ignored that.
I'm adding an answer because a comment would be too long. I originally had a very long, verbose explanation about how async works and await work. But it's just so convoluted that actual data may just be easier to understand. So here is the, uh, simplified explanation. Note: This is run on Chrome v97, FireFox v95, and Node v16 with the same results.
The answer as to what's faster: it depends on what you're returning and how you're calling it. await works differently than async because it runs PromiseResolve (similar to Promise.resolve but it's internal). Basically, if you run await on a Promise (a real one, not a polyfill), await doesn't try to wrap it. It executes the promise as is. That skips a tick. This is a "newer" change from 2018. In summary, evaluating await always returns the result of a Promise, not a Promise, while avoiding wrapping Promises when possible. That means await always takes at least one tick.
But that's await and async doesn't actually use this bypass. Now async uses the good ol' PromiseCapability Record. We care about how this resolves promises. The key points are it'll instantly start fulfilling if the resolution is "not an Object" or if .then is not Callable. If neither are true, (you're returning a Promise), it'll perform a HostMakeJobCallback and tack on to the then in the Promise, which basically means, we're adding a tick. Clarified, if you return a Promise in an async function, it'll add on an extra tick, but not if you return a Non-Promise.
So, with all that preface (and this is the simplified version), here's your chart as to how many ticks until your await foo() call is returned:
Non-Promise
Promise
() => result
1
1
async () => result
1
3
async () => await result
2
2
This is tested with await foo(). You can also test with foo().then(...), but the ticks are the same. (If you don't use an await, then the sync function would indeed be 0. Though foo().then would crash, so we need something real to test with.) That means our floor is 1.
If you understood my explanations above (hopefully), this will make sense. The sync function makes sense because at no point in the function do we call for paused execution: await foo() will take 1 tick.
async likes Non-Promises and expects them. It will return immediately if it finds one. But if it finds a Promise, it'll tack on to that Promise's then. That means it'll execute the Promise (+1) and then wait for the then to complete (another +1). That's why it's 3 ticks.
await will convert a Promise to a Non-Promise which is perfect for async. If you call await on a Promise, it'll execute it without tacking any extra ticks (+1). But, await will convert a Non-Promise into a Promise and then run it. That means await always takes a tick, regardless of what you call it against.
So, in conclusion, if you want the fastest execution, you want to make sure your async function always includes at least one await. If it doesn't, then just make it synchronous. You can always call await on any synchronous function. Now, if you want to really tweak performance, and you are going to use async, you have to make sure always return a Non-Promise, not a Promise. If you are returning a Promise, convert it first with await. That said you can mix and match like this:
async function getData(id) {
const cache = myCacheMap.get(id);
if (cache) return cache; // NonPromise returns immediately (1 tick)
// return fetch(id); // Bad: Promise returned in async (3 ticks)
return await fetch(id); // Good: Promise to NonPromise via await (2 ticks)
}
With that in mind, I have a bunch of code to rewrite :)
References:
https://v8.dev/blog/fast-async
https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-async-functions-abstract-operations-async-function-start
Test:
async function test(name, fn) {
let tick = 0;
const tock = () => tick++;
Promise.resolve().then(tock).then(tock).then(tock);
const p = await fn();
console.assert(p === 42);
console.log(name, tick);
}
await Promise.all([
test('nonpromise-sync', () => 42),
test('nonpromise-async', async () => 42),
test('nonpromise-async-await', async () => await 42),
test('promise-sync', () => Promise.resolve(42)),
test('promise-async', async () => Promise.resolve(42)),
test('promise-async-await', async () => await Promise.resolve(42)),
]);
setTimeout(() => {}, 100);