The super short answer is that .then()
came first and there was no reason to remove it from the language when await
came along later. In fact, even now that we have await
, there are still reasons when .then()
and/or .catch()
are more convenient or more suited for the task than await
. Here are some of those reasons:
- It predates
await
and was the only way to get values out of a promise for several years.
- There are situations where
p.then(fnResolve, fnReject)
is useful.
- There are situations where a single
p.catch()
or p.then().catch()
is easier and simpler than using await
with try/catch
around it.
- There are situations where you don't want the entire function to be suspended like
await
will do, but you still want a handler for when the promise is done.
- We do not yet have top level
await
in many environments yet so with top level promises, the only way to get the value out at the top level is to use .then()
or wrap it in a non-top level function.
- Higher level promise control flow functions such as
Promise.all()
, Promise.race()
and Promise.allSettled()
and the many others that people have designed themselves all use .then()
to monitor multiple promise chains that are in-flight at once because they don't want to force sequencing which is what await
is designed for. In other words, await
is designed to help you sequence things one after another. It lets you create a promise chain and "waits" for it to finish before executing the next line of code. Sometimes, that is not the desired control flow. Sometimes you want things in flight at the same time, not sequenced.
So, while await
can replace .then()
in many cases, they are not identical and there are still situations where .then()
is useful instead of await
and, of course, it existed first so it isn't about to be removed from the language.
Discussion of item #1:
Before await
, the only way to get a value out of a promise was with .then()
and there was no need to remove .then()
when await
was later added.
someAsynchronousFunc().then(function(result) {
console.log(result);
}).catch(function(err) {
console.log(err);
});
I even used regular functions because promises predated arrow functions too.
Discussion of item #2:
As Bergi shows here, there are times when it's easier to isolate one specific error source with the structure:
p.then(fnResolve, fnReject)
You can, certainly achieve the same result with await
, but not as cleanly. I won't repeat the above referenced answer here. Please read it for details.
Discussion of item #3:
There are situations where a single p.catch()
or p.then().catch()
is easier and simpler than using await
with try/catch
around it.
I regular see this when I have a bunch of logic involving asynchronous operations and then there's some final cleanup at the end like closing files. I don't need to await
that as the rest of the flow doesn't need to wait for it, I don't intend to propagate a close error, I do need to catch errors and I do need to log the error. So, I choose to do something like this:
async function someFunc() {
let fileHandle;
try {
... multiple pieces of business logic using await
} catch(e) {
... handle errors in business logic
} finally {
if (fileHandle) {
fileHandle.close().catch(err => {
console.log("Error closing file", err);
});
}
}
}
I choose not to use await
primarily because I don't need someFunc()
to wait for the close()
operation to complete and I'm not trying to propagate any error with close()
, particularly if all I was doing was reading from the file. A close error is both highly unlikely (when only reading) and also of no real consequence. This could be done with await
, but it would both be more code and would wait when I don't want it to wait. The single .catch()
is just simpler.
Discussion of item #4:
There are situations where you don't want the entire function to be suspended like await
will do, but you still want a handler for when the promise is done.
This would mostly occur when you're branching your promise chain into a separate, independent promise chain. This isn't that common a pattern, but does occasionally happen. Let's suppose that you have some middleware that collects statistics in a separate database server. You'd like to know if there are errors in recording the statistics, but it's not something you want to communicate back to the user and it's not something you want the response back to the http request to "wait" for. In this case, you want to fire off a new separate promise chain to record the statistics that you main promise chain does not wait for.
app.use("/update", (req, res, next) => {
// update statistics in separate database
updateStats(req).catch(err => {
// log error
console.log("updateStats error", req.url, err);
});
// continue on the request chain without waiting for stats update to finish
next();
});
Discussion of item #5:
We do not yet have top level await in many environments yet so with top level promises, the only way to get the value out at the top level is to use .then()
or wrap it in a non-top level function.
If you have a promise at the top level, then you can do this:
someAsyncFunc(...).then(result => {
// do something with result
}).catch(err => {
// do something with error
});
Or, you can do something like this:
(async function() {
try {
let result = await someAsyncFunc(...);
// do something with result
} catch(e) {
// do something with error
}
})();
There are times when the 2nd form may actually be the desirable solution, particularly if you're trying to sequence multiple asynchronous operations, all with await
. But, if the solution is no more complex than what we have above, the async
IIFE is clearly a contrived structure just to allow the user a single await
. It's not simpler or cleaner.
Discussion of item #6:
Higher level promise control flow functions such as Promise.all()
, Promise.race()
and Promise.allSettled()
and the many others that people have designed themselves (such as mapConcurrent()
that I mentioned below) all use .then()
and/or .catch()
to monitor multiple promise chains that are in-flight at once.
If you're going to have multiple asynchronous operations in flight at once, you (somewhat by definition), do not want to await
each individual one. Instead, you want to launch them all, use some code that monitors them all and then provide some summary information when a specific condition has been met. With Promise.all()
, you want to know when the first one rejects or when all have succeeded. With Promise.race()
, you want to know when the first one rejects or succeeds. With Promise.allSettled()
, you want to know when they are all done (whether successfully or not). None of these are implemented with await
because all the asynchronous operations are launched in parallel. await
is designed to serialize asynchronous operations. It doesn't do parallel operation.
For example, if you want to fetch an array of URLs, you might do this:
Promise.all([url1, url2, url3].map(url => {
return got(url);
})).then(arrayOfData => {
console.log(arrayOfData);
}).catch(err => {
console.log(err);
});
This particular code could use await
, like this:
try {
let arrayOfData = await Promise.all([url1, url2, url3].map(url => {
return got(url);
});
console.log(arrayOfData);
} catch(err) {
console.log(err);
}
But, internal to the implementation of Promise.all()
or any other function that is managing N promises in flight at the same time, it doesn't want to use await
because it doesn't want to serialize the operations, it wants to allow them to run in parallel and monitor them in parallel with .then()
and .catch()
(not serialize them with await
).
Here's an example of a function named mapConcurrent()
that lets you process an array of items, calling some asynchronous function on each item in the array, but rather than running the entire array in parallel, it runs N operations at a time in parallel (to conserve memory usage and in some cases to keep from overwhelming the target server or avoid rate limiting). That implementation, like others that do something similar to manage parallel asynchronous operations using promises, all use .then()
and .catch()
to monitor multiple operations in flight at the same time.