m0meni
m0meni

Reputation: 16435

Are there benefits to wrapping existing API's callbacks in promises to avoid "callback hell"?

So at this point promises seem to have much more traction as "best practice" than callbacks, but many existing libraries still make use of callbacks.

So given a library that already implements a callback pattern like this:

library.connect(function(err) {
  library.someQuery({}).exec(function(err, result) {
    // some code
    library.someQuery(result).exec(function(err2, result2) {
      // some code
    })
  })
})

Is there benefit to wrapping these callbacks in promises to avoid the nesting?

new Promise((resolve, reject) => {
  library.connect(function(err) {
    if (err) reject(err)
    else resolve()
  }
}).then(() => {
  return new Promise((resolve, reject) => {
    library.someQuery({}).exec((err, result) => {
      if (err) reject(err)
      else resolve(result)
    }
  })
}).then((result) => {
  return new Promise((resolve, reject) => {
    library.someQuery(result).exec(function(err2, result2) {
      if (err) reject(err2)
      else resolve(result2)
    }
}).then((result) => {
  // some code
}).catch((err) => // handle error)

It's nicer without the nesting, but it's a lot more verbose. Also I'm not sure how much added benefit this would have. Maybe nicer error handling?

Upvotes: 0

Views: 62

Answers (2)

nrabinowitz
nrabinowitz

Reputation: 55678

This is primarily opinion, but:

  • If you're doing this more than once, it's probably worth writing a facade for the library that exposes a Promise-based API:

    import library from 'library';
    
    export default {
        connect() {
            return new Promise((resolve, reject) => {
              library.connect(function(err) {
                if (err) reject(err)
                else resolve()
              }
            }
        }
        // etc
    }
    
  • Callback hell / nesting is an implementation problem, not a fundamental issue of callbacks, and can often be improved by breaking out functions rather than nesting. Libraries like async make callbacks as legible as Promises, or more so.

  • One advantage(?) of promises is that you can use the async/await syntax (assuming Babel transpiling). In practice I've found this syntax tricky, but some folks really like it.

Upvotes: 1

Andreas Louv
Andreas Louv

Reputation: 47099

In your first snippet you have multiply places to handle errors, while with promises you only have one.

I think you make some good observations, also add to the case there will be a slight overhead on performance using promises. But you might get an easier debug able code using them, if you can live the the repeated code.

You can also make a small function to abstract away the promises, but keep in mind this will add a little more overhead:

// Non tested code, but hopes it shows a point
function make_promisable(context, method) {
  return new Promise((resolve, reject) => {
    context[method]((err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
};
// Note: The below actually seems to hide the logic in the code,
//   is this any good, or harder to read/debug?
make_promisable(library, 'connect').then(() => {
  return make_promisable(library.someQuery({}), 'exec');
}).then((result) => {
  return make_promisable(library.someQuery(result), 'exec');
}).then((result) => {
  // some code
}).catch((err) => // handle error)

Upvotes: 2

Related Questions