What's wrong with awaiting a promise chain? - javascript

I’m working on an Angular 6 application and I’ve been told the following is an anti-pattern:
await someFunction().then(result => {
console.log(result);
});
I realize that it is pointless to await a promise chain. If someFunction() returns a promise, you don’t need a promise chain if you’re awaiting it. You can do this:
const result = await someFunction();
console.log(result);
But I’m being told awaiting a promise chain can cause bugs, or that it will break things in my code. If the first code snippet above does the same thing as the second snippet, what does it matter which one is used. What dangers does the first snippet introduce that the second one doesn’t?

I’m being told awaiting a promise chain will break things in my code.
Not necessarily, your two code snippets do indeed work the same (as long as someFunction() really returns a promise).
What does it matter which one is used. What dangers does the first snippet introduce that the second one doesn’t?
It's harder to understand and maintain, it's confusing to mix different styles. Confusion leads to bugs.
Consider that you would need to add another promise call at the location of the console.log() call, or even a conditional return from the function. Can you use await in the callback like elsewhere in the function, do you need to return the result from the then callback, is it even possible to return from the outer function? All these questions don't even come up in the first snippet. And while they can be easily answered for your toy example, it might not be as easy in real code with more complex and nested control flow.
So you should prefer the more concise and clean one. Stick to await for consistency, avoid then in async functions1.
1: Of course, there's always an exception to the rule. I would say that it's cleaner to use promise chaining for error handling in some cases where you would use catch or the second then callback.

Under the hood, async/await is just promises.
That is, when you have some code that looks like:
const result = await myAsyncFunction();
console.log(result):
That's exactly the same as writing:
myAsyncFunction().then(data => {
const result = data;
console.log(result);
});
The reason then - that you shouldn't mix async/await and .then chains - is because it's confusing.
It's better to just pick one style, and stick to it.
And while you're picking one - you might as well pick async/await - it's more understandable.

If your then code returned a promise instead of calling console.log, your first example would await, but your second example would not.
When you use async/await, you will catch your rejects in try/catch blocks. Your code will be less nested and clearer.
Using then often results in more nesting, and harder to read code.
You can await anything, whether or not it returns a promise. Sometimes this future-proofs calling a method that may one day become async or just return a promise without declaring async.
The downsides are complexity, performance, and compatibility, all of which pale in comparison to the gains.
I find that if you rely on a function's return value after calling it, and it is or may eventually become asynchronous, decorate calling your functions with await to your heart's delight, whether or not it is current async or returns a promise.

Related

Return an object with a "then" function within "Promise.then()"

Within my Node.JS application I have written a function (findByReference) that goes to a database and asynchronously yields a fetched database row. I have written this function using Promises. Additionally, I have written an implementation of the Maybe monad and want my findByReference function to yield an instance of Maybe.
My code looks like the below:
findByReference(r)
.then(raw => raw ? Just(raw) : Nothing())
.then(row => {
(row instanceof Maybe) === true;
});
Without going into what Just and Nothing mean, the implication of this (because of how I've written Maybe) is that the row variable in the above code has a function on it called "then". To cut a long story short, it appears that Javascript is getting confused and is for some reason automatically calling MY "then" and instead of passing the Maybe is actually passing to the callback whatever MY "then" returns as the value of row. This is obviously leading to all manner of weird behaviour. If I simply remove the "then" function from my object then it all works as expected.
I am aware that if a Promise.then returns another Promise, then execution will pause until that promise is resolved. I have been unable to find any official documentation to back this up, but is it the case that this decision is simply based on the existence of a "then" function (the closest I have found is this https://developers.google.com/web/fundamentals/primers/promises which refers to the return value as "something Promise-like"). If this is the case, it would be my understanding that "then" as a function name is basically a reserved word in Javascript? I have seen other implementations of Maybe (such as this one https://www.npmjs.com/package/data.maybe) that use the word "chain" for a similar thing - I wondered if this is why?
Can anyone shed any light on if my deduction here is correct and if so is there any workaround I can use other than renaming my function?
FYI the only other SO question I've found that touches this problem is this one - Resolve promise with an object with a "then" function - but since that is angular-specific I don't believe this is a duplication.
Thanks in advance!
...the row variable in the above code has a function on it called "then". To cut a long story short, it appears that Javascript is getting confused and is for some reason automatically calling MY "then"...
It's not confused. :-) This is the definition of how promises work. JavaScript's promises work according to the Promises/A+ specification, which uses this terminology:
1.1 “promise” is an object or function with a then method whose behavior conforms to this specification.
1.2 “thenable” is an object or function that defines a then method.
If you have an object passing through a promise chain that's a thenable but not a promise, it's incompatible with promises.
So yes, in a sense, the then property of objects passing through promise chains is "reserved" by the Promises/A+ spec. You'll need to wrap your raw value in an object that doesn't have a then (and then unwrap it later). Or if you can, rename then in your design to remove the conflict.

resolving an array of promises from within a parent promise

This is my first shot at nested promises. I'm using the bluebird library but I think the idea is the same for all of the promise libraries.
At a high level, this is what I'm trying to do:
myService.getSomeData(url)
.then((data) => {
myOtherService.getMoreData(data.uniqueId)
.then((thisDataIsAnArray) => {
//loop over the data above and do something
});
});
getMoreData() is supposed to make X service calls and store the results in an array X elements long. This is where I start getting lost, in that I'm not sure how to craft this method and what I should be returning from it. I've taken a few stabs at bluebird's Promise.all and Promise.map but am floundering and thought I'd solicit suggestions.
Promises are just return values you attach callbacks to, instead of passing callbacks into functions. Unless you return all of them, there's no way for the callbacks to chain, or catch all their errors.
Also, return from all the .then's the instant you have another promise. This flattens things.
Promise iteration totally warped my brain the first time I tried it as well. I think Bluebird's documentation does a fairly poor job distinguishing the common use cases but I'll not go on about it because (a) I love Bluebird, and (b) I don't have the time to update the docs.
I feel like Promise.map is the right thing for your scenario.
myService.getSomeData(url)
.then((data) =>
{
return myOtherService.getMoreData(data.uniqueId)
})
.map((item) =>
{
return doSomethingWithData(item);
})
.then((results) =>
{
// do something with the result array.
});
Depending on what you want to do with the results, where I've used .map you can also use .reduce, or .each. Note that .each does not modify the return value from the promise to which it's chained, hence the "use only for side-effects" comment in the Bluebird docs.
The difference between the instance and static methods is, of course, that with static you must supply the array, e.g. Promise.map(array, (item) => {}).
Also, as #jib said - always return a value inside your callbacks. This will save you much pain down the line.

Chaining multiple .then, some functions without asynchronous code. Best practise?

I have one function that runs asynchronously, such as querying records from a database. The remaining code depends on the result, but runs synchronously. I am struggling to understand a good way to deal with this and would like to know the best practise?
Imagine the following code:
var usersDataPromise = getUsersDataFromDB();
var usersAgeArray = usersDataPromise.then(extractAges);
var averageAge = calculateAverageAge(usersAgeArray);
here, getUsersDataFromDB() runs synchronously and returns a promise. When the promise resolves, it runs extractAges(), a function that simply takes some of that data and returns an array. This function is sync. I want to pass that on to calculateAverageAge(), but this is where it breaks: calculateAverageAge does not wait for getUsersFromDB() to finish, and simply starts as soon as it can (usersAgeArray will be undefined).
What I tried:
I can make extractAges() return a promise instead of an array, and make calculateAverageAge wait with .then, by doing:
var usersDataPromise = getUsersDataFromDB();
var usersAgeArrayPromise = usersDataPromise.then(extractAges);
var averageAge = usersAgeArrayPromise.then(calculateAverageAge);
However, it feels as if I made the code needlessly complicated: I made extractAges() return a promise even though it runs asynchronously, not synchronously.
I realize I could have called calculateAverageAge() at the end of the extractAges function, but I'm trying to learn how to keep the code clean, and this feels as if I'm introducing side effects to a function that should only be extracting an array. Now the function description seems not fully accurate: it extracts an array but also calls on the next function.
I hope I've made this clear, I'm having trouble wrapping my head around it. What is the best way to deal with this? Thanks!
I can make extractAges() return a promise instead of an array
There's no reason to do that. Only because you are using a function in a then callback, it doesn't need to return promises.
and make calculateAverageAge wait with .then
Yes, you need to use then anyway, because the array is produced asynchronously and usersDataPromise.then(extractAges) always returns a promise. There's no alternative.
var averageAgePromise = getUsersDataFromDB().then(extractAges).then(calculateAverageAge);

Why are callbacks more "tightly coupled" than promises?

Can you explain me the following phrase (taken from an answer to Stack Overflow question What are the differences between Deferred, Promise and Future in Javascript?)?
What are the pros of using jQuery promises against using the previous jQuery callbacks?
Rather than directly passing callbacks to functions, something which
can lead to tightly coupled interfaces, using promises allows one to
separate concerns for code that is synchronous or asynchronous.
A promise is an object that represents the result of an asynchronous operation, and because of that you can pass it around, and that gives you more flexibility.
If you use a callback, at the time of the invocation of the asynchronous operation you have to specify how it will be handled, hence the coupling. With promises you can specify how it will be handled later.
Here's an example, imagine you want to load some data via ajax and while doing that you want to display a loading page.
With callbacks:
void loadData = function(){
showLoadingScreen();
$.ajax("http://someurl.com", {
complete: function(data){
hideLoadingScreen();
//do something with the data
}
});
};
The callback that handles the data coming back has to call hideLoadingScreen.
With promises you can rewrite the snippet above so that it becomes more readable and you don't have to put the hideLoadingScreen in the complete callback.
With promises
var getData = function(){
showLoadingScreen();
return $.ajax("http://someurl.com").promise().always(hideLoadingScreen);
};
var loadData = function(){
var gettingData = getData();
gettingData.done(doSomethingWithTheData);
}
var doSomethingWithTheData = function(data){
//do something with data
};
UPDATE: I've written a blog post that provides extra examples and provides a clear description of what is a promise and how its use can be compared to using callbacks.
The coupling is looser with promises because the operation doesn't have to "know" how it continues, it only has to know when it is ready.
When you use callbacks, the asynchronous operation actually has a reference to its continuation, which is not its business.
With promises, you can easily create an expression over an asynchronous operation before you even decide how it's going to resolve.
So promises help separate the concerns of chaining events versus doing the actual work.
I don't think promises are more or less coupled than callbacks, just about the same.
Promises however have other benefits:
If you expose a callback, you have to document whether it will be called once (like in jQuery.ajax) or more than once (like in Array.map). Promises are called always once.
There's no way to call a callback throwing and exception on it, so you have to provide another callback for the error case.
Just one callback can be registered, more than one for promises, and you can register them AFTER the event and you will get called anyway.
In a typed declaration (Typescript), Promise make easier to read the signature.
In the future, you can take advantage of an async / yield syntax.
Because they are standard, you can make reusable components like this one:
disableScreen<T>(promiseGenerator: () => Promise<T>) : Promise<T>
{
//create transparent div
return promiseGenerator.then(val=>
{
//remove transparent div
return val;
}, error=>{
//remove transparent div
throw error;
});
}
disableScreen(()=>$.ajax(....));
More on that: http://www.html5rocks.com/en/tutorials/es6/promises/
EDIT:
Another benefit is writing a sequence of N async calls without N levels of indentation.
Also, while I still don't think it's the main point, now I think they are a little bit more loosely coupled for this reasons:
They are standard (or at least try): code in C# or Java that uses strings are more lousy coupled than similar code in C++, because the different implementations of strings there, making it more reusable. Having an standard promise, the caller and the implementation are less coupled to each other because they don't have to agree on a (pair) of custom callbacks with custom parameters orders, names, etc... The fact that there are many different flavors on promises doesn't help thought.
They promote a more expression-based programming, easier to compose, cache, etc..:
var cache: { [key: string] : Promise<any> };
function getData(key: string): Promise<any> {
return cache[key] || (cache[key] = getFromServer(key));
}
you can argue that expression based programming is more loosely coupled than imperative/callback based programming, or at least they pursue the same goal: composability.
Promises reify the concept of delayed response to something.
They make asynchronous computation a first-class citizen as you can pass it around. They allow you to define structure if you want - monadic structure that is - upon which you can build higher order combinators that greatly simplify the code.
For example you can have a function that takes an array of promises and returns a promise of an array(usually this is called sequence). This is very hard to do or even impossible with callbacks. And such combinators don't just make code easier to write, they make it much easier to read.
Now consider it the other way around to answer your question. Callbacks are an ad-hoc solution where promises allow for clearer structure and re-usability.
They aren't, this is just a rationalization that people who are completely missing the point of promises use to justify writing a lot more code than they would write using callbacks. Given that there is obviously no benefit in doing this, you can at least always tell yourself that the code is less coupled or something.
See what are promises and why should I use them for actual concrete benefits.

Accessing a previously fulfilled promise result in a promises chain [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 8 years ago.
What is the correct pattern, when coding with promises, to access data coming from long before in a chain of promises?
For example:
do_A.then(do_B).then(do_C).then(do_D).then(do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_IsSuccesfullyCompleted)
My current solution: passing along a single JSON structure through the chain, and let each step populate it.
Any opinion about that?
I don't think there's one "correct" pattern for this. Your solution sounds neat, however, it's a bit tightly coupled. It may work great for your situation, but I see a few problems with it as a general pattern:
Participating steps need to agree on the structure of the collector object.
Every step needs to participate in at least the forwarding of the object, which can get tedious if the chain is long and the need for previous data occurs sporadically. This is also inflexible to insertion of steps not written by you (chaining doesn't always happen linearly).
Said differently: Unless do_A|B|C|D and do_E are under your control, you'll need to wrap them with boilerplate to store off your collector object in a closure and convert to and from the functions' natural inputs and results, since the functions wont be "in on" your pattern.
On the other hand, if the functions are in on it, then the data-dependencies between the steps have effectively become hidden inside the functions. This may look clean, but it could become a maintenance problem. For example: if this is a team project, then someone might think they can re-order your steps, absent any no clue in the call-pattern what inputs do_E requires.
I would suggest a more straightforward approach using closures:
var a, c;
do_A()
.then(function(result) { a = result; return do_B(); })
.then(do_C)
.then(function(result) { c = result; return do_D(); })
.then(function() {
return do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_Succeeds(a, c);
})
.catch(failed);
There's no collector object to define; do_A|B|C|D and do_E can be generic functions without knowledge of any pattern; there's no boilerplate unless returned data is relied on (do_B and do_D); and the data-dependencies (a and c) are explicit.
Yes, this is the correct way to chain state with actions.
Chaining .then statements is very common and is usually our building block when piping things around. It's at the very core of promises.
What you're doing is both correct and idiomatic.
For the curious spirit let's show this.
In order to verify this - we can check the promises specification.
We want to verify that:
It chains
In the case of a rejection, it doesn't call the handler in the chained then
It rejects the next promise returned from then with the same reason
It executes in sequence passing return value.
Let's verify these in order, using the specification - in particular .then:
1. It chains
7.1 then must return a promise [3.3].
Great, let's verify that it also chains on fullfillment
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure >[[Resolve]](promise2, x).
Great, so we know that when our promise resolves or rejects then our then handler is called with the appropriate parameter. So .then(do_A).then(do_B) will always work assuming do_A resolves.
2. In the case of a rejection, it doesn't call the handler in the chained then
7.iv. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.
Great, so it rejects and calls onRejected if it's there, if it doesn't it chains.
3. It rejects the next promise returned from then with the same reason
We just covered that in 2.
4. It executes in sequence passing return value.
That is again
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
So, if you set onFulfilled it'll run the resolution process. The resolution process itself dictates:
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.
If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
Where y is the return value of x.
Great! so it works.

Categories

Resources