Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best practices for promises? #216

Open
PandaWhisperer opened this issue Nov 18, 2014 · 49 comments
Open

Best practices for promises? #216

PandaWhisperer opened this issue Nov 18, 2014 · 49 comments

Comments

@PandaWhisperer
Copy link

First of all, great job on this guide, I agree with it almost 100%. All of this should really be common knowledge, but I sure as hell didn't know most of these things when I started, so kudos for writing it all down.

Now, since promises have been standardized and a lot of people have started using them, it would be nice to have a section on how to keep promise-based code readable. For instance, when chaining multiple promises together, there are at least two alternative ways to do it:

Option 1: anonymous functions

return doSomething(foo).then(function(result) {
  // ...
}).then(function(result2) {
  // ...
}).then(function(result3) {
  // ...
}).catch(function(error) {
  // ...
}).finally(function() {
  // ...
});

Option 2: named functions

function doThis(result) {
  // ...
}
function doThat(result) {
  // ...
}
function handleError(error) {
  // ...
}
function done() {
  // ...
}

return doSomething(foo)
  .then(doThis)
  .then(doThat)
  .catch(handleError)
  .finally(done);

The second way, while being somewhat more verbose, seems to be preferable, as the chain of tasks that the promise result travels through becomes more obvious.

I did an internal poll of our developers, and so far, option 2 seems to be ahead, although I have not heard from everyone yet. How does AirBnB deal with this, and what are your opinions?

@hshoff
Copy link
Member

hshoff commented Nov 20, 2014

Hi @PandaWhisperer, thanks for opening this Issue. Promises are a great place to have conventions in place for clarity.

I'm going to leave this open for a bit to give time for the community to participate in the discussion.

Will follow up on this after a week or so.

@netpoetica
Copy link

Option 2 is definitely more popular in node where we've got module.exports and don't have to worry about those functions leaking globally. I do this on the front-end as well and a lot of people do, but a lot of times, new programmers or non-JS programming who are writing JS, use anonymous functions - and a lot of them write the same exact anonymous functions a dozen times. I.e:

return doSomething(foo).then(function(result) {
  $('#mything').html(result);
}).then(function(result2) {
  $('#mything2').html(result);
}).catch(function(error) {
  console.log("There was an error!", error);
});

return doSomethingElse(foo).then(function(result) {
  $('#mything').html(result);
}).then(function(result2) {
  $('#mything2').html(result);
}).catch(function(error) {
  console.log("There was an error!", error);
});

This is a really minimal example, but what I'm getting at is that we should push to recommend option 2 for best practices, particularly calling out at least these two major values I can think of:

  • ability to reuse named functions, saving memory and centralizing locations that will need to change
  • named functions provide much more useful stack traces in error messages

Good work on this!! I'm looking forward to seeing what people have to say about it. It could also be useful to recommend promise implementations based on various criteria:

  • most popular libs
  • most lightweight
  • most package manager support

@PandaWhisperer
Copy link
Author

Here's another situation I just stumbled upon while working with some Promise-based code:

getPromise().then(function(result) {
    if (!checkResult(result)) {
       throw new Error("invalid result!");
    } else {
       return doSomething(result);
    }
}).catch(handleError);

Obviously, this code is a bit contrived, but bear with me here, the point should become obvious in a second. The above code is completely equivalent to this:

getPromise().then(function(result) {
    if (!checkResult(result)) {
       return Promise.reject(new Error("invalid result!"));
    } else {
       return Promise.resolve(process(result));
    }
}).catch(handleError);

Of course, .then(), by design, ALWAYS returns a promise, so making this explicit does not add anything. You'd think that would make it obvious which version is preferable – but the fact that the code I have in front of me right now uses the 2nd version shows otherwise.

@dyoder
Copy link

dyoder commented Dec 2, 2014

I'm not a fan of using named functions if the only reason is for the names to show up in stack traces. Part of the appeal of promises (and, more generally, functional programming) is to compose complex behaviors from simple ones, and anonymous functions are an important part of making that easier to do.

Of course, if we have a complex behavior that can be reused, that's a different story. But for, say, up to 3 lines of code (a completely arbitrary threshold), anonymous functions are fine.

The issue of handling typical error callbacks, though, suggests those functions should be lifted by your promise library. That is, I don't think you should typically need to map errors to reject and results to resolve.

If you're not using some standard callback form (for which lifting functions would be available), one question is: why not? And another is: does your particular callback style come up often enough to write your own standard lifting function.

@getvega
Copy link

getvega commented Mar 31, 2015

I definitely +1 @dyoder on that. Sure it helps debugging but named functions require the developer to 1/ find a good name 2/ keep that name consistent over time with the rest of the code base.

In ES6 in some cases it will become even more elegant / simple :

getPromise().then((response) => response.body)

Instead of

getPromise().then(function readBody(response) {
     return response.body;
})

@getvega
Copy link

getvega commented Mar 31, 2015

About best practices, seems like you all put the .then on the same line instead of a new line...

What do you think of

getPromise()
    .then(function(response) {
         return response.body;
    })
    .then(function(body) {
         return body.user;
    });

It seems a bit more compliant with leading dots in https://github.com/airbnb/javascript#whitespace

@JoshuaJones
Copy link

Definitely leaning more to have .then on a new line per @getvega's last comment, and it does line up with the guides direction for long chains.

Looks a lot more readable.

@calmdev
Copy link

calmdev commented Apr 3, 2015

I came here looking for indentation guidelines for promises and found this issue. I use the same indentation style as suggested by @getvega and agree with @JoshuaJones on it's readability. As for the original issue, as other have pointed out, it'd be a mix of the two options rather than just one or the other. The es6 fat arrows are nice for readability when you want to use closures, but there is also the benefit of the named functions for the stuff you want to reuse in other places. @PandaWhisperer makes good points too when catching rejections.

@thomasvanlankveld
Copy link

In general, I would like to promise practices to be as close to the use of async/await as possible. (currently stage 3)

Errors

@PandaWhisperer's error thing code is a good example, the first one avoiding Promise.reject and Promise.resolve is closer to what this would look like with async/await:

try {
  const result = await getPromise();
  if (!checkResult(result)) throw new Error("invalid result!");
  doSomething(result);
} catch (errr) {
  handleError(errr);
}

Anonymous vs named

Concerning @PandaWhisperer's original question: I think whether to use anonymous or named functions should really be up to the developer, based on his situation; As it is with synchronous code (should I extract this to be a subroutine or not?). Consider his exampled refactored into async/await:

try {
  const result = await doSomething(foo);
  const result2 = // ...
  const result3 = // ...
  // ...
} catch (errr) {
  // ...
} finally {
  // ...
}

Whether result2 is best obtained with a function doThis or with one or more lines of code really depends on the situation.

I'm not saying that because it depends on the situation with async/await, it also does for promises. I would actually prefer promises to be written with a set of well-named functions over them being written with a set of anonymous ones, but that's my personal preference; not what I would want in a style guide.

Line breaks

I like @getvega's suggestion to always put .then on a new line. This seems consistent with the chaining style.

Arrow functions

I especially like @getvega's arrow function suggestion. Applying this to his second example:

getPromise()
    .then(response => response.body)
    .then(body => body.user);

Makes it easy to see we could even turn this into:

getPromise().then(res => res.body.user);

Badabing!

@PandaWhisperer
Copy link
Author

Wow. This issue is more than a year old. Has the guide been updated with regards to promises? Just did a quick check and it seems that while it has been updated for ES6, promises still aren't mentioned anywhere.

FWIW these days I generally prefer the async/await style, but using generator functions and yield.

@ljharb
Copy link
Collaborator

ljharb commented Feb 16, 2016

Duplicate of #597 as well.

We will be updating the guide to discuss Promises and async/await as soon as possible. Generators + yield would be an antipattern in whatever we write up, fwiw, as generators are synchronous.

@PandaWhisperer
Copy link
Author

Generators + yield would be an antipattern in whatever we write up, fwiw, as generators are synchronous.

How is that relevant? Yes, a generator runs synchronously, but only until it is eventually suspended when waiting for the promise to resolve. The promise still works asynchronously.

async/await are ES7 only, while generators and yield can be used today.

@ljharb
Copy link
Collaborator

ljharb commented Mar 31, 2016

async/await is not in ES7, but may be in ES2017.

One of the primary issues is that generators can't be used without the regenerator runtime, which is very very heavyweight for dubious benefit. We don't yet endorse async/await because of the same requirement.

@kevinsimper
Copy link

You aren't using Promises at Airbnb?

@ljharb
Copy link
Collaborator

ljharb commented Jan 27, 2017

Of course we do.

@kevinsimper
Copy link

Okay, cool, I was not asking in a provoking way, more asking because it was not clear if Airbnb was using promises, because I thought this covered everything that Airbnb was using, also because this issue has been open for 2 years 😄

@tarwin
Copy link

tarwin commented Mar 27, 2017

I was looking on some good advice for naming of promise functions. It seems that @ljharb closed the discussion "in favor of" this one (#848). Sadly, I don't see any deep discussion on naming here either.

Even when only writing for myself I've found I've created all to many bugs without having a specific naming convention for my libraries that use promises. Yes, I realise documentation is the "real" way to do it, but because JS lacks typing and generally lacks hinting (I know there are solutions) it would be nice.

I agree that asyncMethod() is not really the right way to do things, as when you go into node a whole lot of std lib methods are "async by default" using callback method. Those that aren't end up being called syncMethod() so it just gets confusing.

That said, I don't have a great suggestion myself. I might try some of these and see how they look/feel in the code:

pMethod()
promMethod()
methodP()

I think the last one is likely the best because you're not starting with typing a "p" and the code completion would make more sense.

I'm sure others have way better ideas ..

@ljharb
Copy link
Collaborator

ljharb commented Mar 27, 2017

I don't think a "p" notation is needed; functions returning booleans (predicates) aren't prefixed with "b" either.

Knowing what a function returns is something that ideally should be conveyed in the name - ie, a predicate named "isFoo" clearly returns a boolean - but the assumption should be that everything async returns a promise, and it should ideally be apparent in the name of the function that it's async. If it's not, documentation indeed is the proper way to do it (or more accurately, referring to the actual function definition).

@tarwin
Copy link

tarwin commented Mar 27, 2017

@ljharb you make a great point, using booleans as an examples does help. The problem is (I only speak for myself) that "is" is specific to Booleans. "is" asks a question that begs a yes or no answer.

The same way that you'd expect "numberOfCats" to be an integer, or "amountPaid" to be a float.

The problem with assuming that "everything async returns a function" is that you're writing everything from scratch. Or even rewriting everything. A lot of the time you're not that lucky! I realise that means you're also going to not be lucky in the fact that libraries generally will lack some kind of formal naming. But isn't this discussion about trying to encourage people to best practices so you can at least hope?

When you look at code that use Promises, truthfully it ends up being obvious what is a Promise and what is not though, of course, as you'll always have then()s.

@ljharb
Copy link
Collaborator

ljharb commented Mar 28, 2017

You should be wrapping any callback-taking async lib with a Promise-returning function.

@trajano
Copy link

trajano commented May 2, 2017

I'd go with

return doSomething(foo).then(result => {
  // ...
}).then(result2 =>{
  // ...
}).then(result3 =>{
  // ...
}).catch(error =>{
  // ...
}).finally(() => {
  // ...
})

Should be fine as long as you don't use this

@PandaWhisperer
Copy link
Author

PandaWhisperer commented May 9, 2017

@kevinsimper Indeed, this one of the oldest issues I've opened on Github that I still regularly get updates about.

At this point, I try to avoid any promise chaining whenever possible and just use async/await if possible, or the generator function-based workaround for pre-ES7 environments (usually with some syntactic sugar).

At least in CoffeeScript, this lets you write code that is practically identical with the new async/await syntax. In JavaScript, it's a fair bit uglier because you have to wrap your function definition into another function call.

Not sure what AirBnB's stance is on async/await (couldn't find it in the style guide, probably because it only deals with ES6), but looks like generators are not welcome. Interestingly, after 2 years, they still don't even mention promises in the style guide. 🤔

@ljharb
Copy link
Collaborator

ljharb commented May 9, 2017

@PandaWhisperer we do not allow async/await, primarily because transpiling them requires regenerator-runtime, which is very heavyweight.

await, however, is mostly just sugar for nested promise .thens, and promise chaining is a perfectly fine pattern - one that you can't avoid knowing about or using, even with async functions, which are just sugar for a promise-returning function (and thus always require a .catch at least).

@davidvedvick
Copy link

I just happened upon this conversation while browsing the internet for an answer to this exact question. However my personal convention, which I quite enjoy, involves replacing the get in method names with promise - so something like @tarwin's "numberOfCats" would become promiseNumberOfCats(): Promise<Number>, clearly signifying that the method promises to return the number of cats.

@ifeltsweet
Copy link

I've never been a fan of indenting chains however this especially applies to promises because their whole nature is to represent async code in linear style.

For me, first problem with indenting chains is that now closing brackets misalign, sometimes making you think you forgot a curly bracket.

Second, is that indentation sort of implies that indented methods are children of the first method, where for me they are more of siblings following each other in linear/sync fashion. That is what promises try to achieve in the first place.

So I usually go for this:

getPromise()

.then(function(response) {
  return response.body;
})

.then(function(body) {
  return body.user;
})

.catch(function(err) {
  // handle error
});

or

getPromise()

.then(extract)
.then(parse)

.catch(handleError);

In my opinion this is more readable and looks more synchronous.

@ljharb
Copy link
Collaborator

ljharb commented Jul 24, 2017

Code that's not sync shouldn't strive to look sync. That's not the "whole nature" of promises either - promises are composable.

@kelvne
Copy link

kelvne commented Jul 27, 2017

@ljharb I agree with you. And chaining looks more 'functional'.

@surruk51
Copy link

Apart from verbosity, can anyone see anything wrong with using waitfor_ as a prefix for promise returning functions? To me it improves readability enormously (and thus reduces the possibility of coding errors), but maybe I am misleading myself in some cases by using such a prefix?

@ljharb
Copy link
Collaborator

ljharb commented Nov 13, 2017

@surruk51 #216 (comment), but also "wait for" is the wrong term imo, because "wait" connotes blocking.

@surruk51
Copy link

As a bald statement I think you would be right. However the promise is in this context:
.then(waitfor_something)
.then(waitfor_somethingelse)
i.e. it is not the machine waiting, but my logic.

@ljharb
Copy link
Collaborator

ljharb commented Nov 13, 2017

there's no need for the prefix there tho; you can have the camelCase name of the function make it clear that it's making an async call - but also, its presence in the .then should convey that already.

@chandanch
Copy link

Chaining promises is fine but the chain should not go beyond 2 max. 3

@dbkaplun
Copy link

dbkaplun commented Apr 11, 2018

Here is an eslint plugin for promises that the styleguide could probably use (with some modifications): https://github.com/xjamundx/eslint-plugin-promise

@antgonzales
Copy link

antgonzales commented May 15, 2018

@ljharb do you not use babel-polyfill if you opt out of using regenerator runtime?#216 (comment)

@ljharb
Copy link
Collaborator

ljharb commented May 15, 2018

@antgonzales absolutely we do not, we use airbnb-browser-shims.

@Malfustheone
Copy link

Malfustheone commented Jul 26, 2018

Regarding naming, I have been using the unofficial Observable '$' convention in codebases where I do not have access to Observables. addID$(id).then(doSomething); On the one hand, it is bad to confuse the dev as to what kind of async structure they are getting back. On the other, I rarely mix Promises and Observables.

@Frikki
Copy link

Frikki commented Sep 20, 2018

If you aren’t using a type system, such as TypeScript or Flow that shows the return type of the function, having a naming convention is beneficial. We have, before we introduced TypeScript, used somethingToBe() to indicate that it we are dealing with something in the future.

@mojimi
Copy link

mojimi commented Dec 11, 2018

It seems that most people here prefer the indented new line version of chained thens, like so

whenSomethingHappens()
  .then(whenSomethingElse)
  .catch(console.log)
  .finally( () => console.log("finally"))
;

I personally prefer to not indent, because

  1. When I think of a chain of promises I don't think of hierarchy I think of sequence
  2. It gives the notion that the idented calls are subordinates of the first call
  3. It's unnecessary, it feels like a forced block, my eyes immediately think it's an if/else block
  4. Not indenting looks more like the await syntax pattern
whenSomethingHappens()
.then(whenSomethingElse)
.catch(console.log)
.finally( () => console.log("finally"));

// If using await
const something = await whenSomethingHappens();
try {
  await whenSomethingElse()
} catch(e) {
  console.log(e)
}
console.log("finally");

@popham
Copy link

popham commented Dec 18, 2018

I like the inline definitions chained with each then/catch indented, but the leading periods bug me a little. I leave them at the end like my semicolons, e.g.

return doSomething(foo).
  then(result => {
    // ...
  }).
  then(result2 =>{
    // ...
  }).
  then(result3 =>{
    // ...
  }).
  catch(error =>{
    // ...
  }).
  finally(() => {
    // ...
  });

@ljharb
Copy link
Collaborator

ljharb commented Dec 18, 2018

@popham fwiw, our guide strictly requires always using leading dots (as is conventional in the JS ecosystem)

@popham
Copy link

popham commented Dec 18, 2018

@ljharb, thanks for the feedback. I figured that I was cutting against the grain which is why I found this issue. I agree with the reasoning from 19.6, but.... I'm ignoring the Eslint suggestions, because I like chaining repetitions of the same call on a single line, e.g. es.add("e1").add("e2"). Calls that heuristically navigate a space also seem to discredit those linter rules, e.g. home.getKitchen().getDrawer().getKnife(). Meanwhile, the linter suggestions seem to admit my antipattern, as Eslint lists the following as passable by no-whitespace-before-property:

foo.
  bar().
  baz()

@ljharb
Copy link
Collaborator

ljharb commented Dec 18, 2018

That the linter permits it isn't important; the guide prohibits it.

@mojimi

This comment has been minimized.

@ljharb

This comment has been minimized.

@ken107

This comment has been minimized.

@ljharb

This comment has been minimized.

@govind15496
Copy link

govind15496 commented Jan 7, 2023

To use a promise in a React component, you can use the Promise constructor to create a new promise and use the .then() method to specify a callback function to be executed when the promise is resolved.

Here's an example of using a promise to fetch data from an API and then rendering a component with the fetched data:

function Example() {
const [data, setData] = useState(null);

useEffect(() => {
// Create a new promise
const promise = new Promise((resolve, reject) => {
// Fetch data from the API
fetch('https://my-api.com/data')
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});

// Execute the promise
promise.then(data => {
  // Update the component state with the fetched data
  setData(data);
});

}, []);

// Render the component
return (


{data ? (

{data.message}


) : (

Loading data...


)}

);
}

@ZakariaRahimi
Copy link

Thank you!

@ljharb ljharb mentioned this issue Jan 7, 2023
@chandanch
Copy link

thanks for the details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests