Skip to content

Step 2: DRYer Code

Daniel Levy edited this page Aug 16, 2017 · 7 revisions

Detailed Steps

Required Tasks

  1. Promisify Callback-based libs/dependencies w/ Bluebird.promisify, Bluebird.promisifyAll, or denodify.
  2. Replace all occurrences of callback(...) with either a return Promise.reject(err) for errors, or anything else for success (Promises and values are handled the same).
  3. Ensure every function uses return at least once.
  4. Remember you can start a Promise-chain with literals or variables using Promise.resolve(user).
  5. If you don't have a 'starting' value/parameter you can always .resolve() w/o params. Like so: () => Promise.resolve().then(lookupGps).

This may look like a lot of change for a single step, however all these steps are required before your code improves.

The first few times you go through this process it'll seem fraught with peril. With a bit of practice, it actually becomes a very speedy process. I used to take 3+ hours converting a 500-line file. Now the same size task takes about 15-30 minutes. See Common Issues section below.

Recommended Tasks - Code Clarity

  1. Use destructuring for function params. This makes it easier to understand exactly what a function needs, which should make it's purpose clear (assuming a reasonable name). const auth = ({user, pass}) => {}
  2. Conversely, finding the returns in the function should let you quickly reason about the code-execution-path.

The previous 2 steps have a way of forcing you to write focused single-purpose functions.

Common Issues

I understand if Promises are maddening at first.

I used to run into 2 issues seemingly constantly: no visibility into errors and forgetting to return.

It turns out they are potentially related issues.

  • These issues: 'silent exit with or w/o error' or 'empty return value' or hanging .then() have 2-3 general causes:
    • 95% of the time: Failed to return from function.
    • 4% of the time it's a complicated hierarchy - say +3 Promise chains deep. This will resemble callback hell. Don't do this - either break apart the 3 Promise chains, or flatten it out into 1 promise declaration. A good Promise nesting rule-of-thumb: only nest a new Promise 'chain' when inside a collection method callback (i.e. Bluebird's .map, or .mapSeries).
    • 1% it is a bug in calling Promise.reject(new Error) requires valid Error instance. Avoid throwing errors - while bluebird is pretty awesome at handling this, it's too easy to throw from a different async context. The result can be a meaningless stack traces. To get some hint as to where it went haywire enable LongStackTraces, or promisify the async code you're calling & plug it into your Promise chain.

Make sure to read the install guide for Bluebird - look for long stack trace support.

Preview of Diff:

image