Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid wrapping code in a promise callback?

In order not to wrap blocks of code in a promise callback (to save one level of indentation), I sometime do the following:

function myFunction() {
    // Create the promise object and get the "resolve" callback
    let promiseResolve = null;
    const promise = new Promise((resolve, reject) => {
        promiseResolve = resolve;
    });

    // Then later in the code:
    setTimeout(() => {
        // Something slow
        promiseResolve();
    }, 1000);

    // And at the end
    return promise
}

It works but it feels a bit messy. Is there any proper pattern to do this in JavaScript?

like image 538
laurent Avatar asked Dec 24 '22 12:12

laurent


1 Answers

Is there any proper pattern to do this in JavaScript?

Yes, the proper pattern is what you are apparently trying to avoid.

function myFunction(data) {
    return new Promise((resolve, reject) => {
        // put code in here that calls resolve() or reject()
        someAsyncOperation(data, function(err, result) {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

For what you're showing, there's just really no reason to assign the resolve() or reject() handlers outside the scope of the executor function.

Here are some of the benefits to keeping your code inside the Promise executor:

  1. It is automatically throw-safe. If you throw synchronously inside the executor function (accidentally), it will automatically be caught and turned into a rejection for that promise.
  2. If you throw synchronously in your code outside the promise executor function, it will not be caught at all and your function will throw synchronously. It's very bad to return a promise and throw synchronously as that puts a lot of burden on the caller to watch for errors in two separately mechanisms.
  3. You don't make any case for why you should add complication to your code just to avoid putting some code at a level of indentation that you already have anyways.
  4. The promise executor was designed this way for a bunch of good reasons. What you are showing a preference for is essentially the "Deferred" model where you create an object you can pass around that has exposed methods on it for resolving or rejecting. That is purposely not the design center for promises and there was very good thinking on why it is not the way they decided to go. I will see if I can find some of those debates and discussions to reference, but I know I've seen them and they make sense to me.
  5. There are very, very, very occasionally some rationale cases for a Deferred-like interface where the code is significantly simpler using that model. You can make your own Deferred object using the promise executor (which is almost what you're trying to do here), but it really should only be done when there's a proven need for it, not just because you prefer that coding style or want to avoid putting code inside the executor callback.
  6. You have a lot of rep here so I'm guessing you're a fairly experienced guy so I'm surprised you're resisting putting the code inside the executor because Javascript encourages this a LOT. It's an extremely common style (using anonymous callbacks and embedding code logic in those callbacks). Even promise .then() and .catch() require that. To program in Javascript, one has to be used to that and expect it. It is often required in order to access parent scoped variables too.
  7. In general, you don't want to mix callback and promise code in the same logic flow. So, when I want to use a promise interface with non-promise-based callback interfaces, the first thing I do is I make a promisified interface for the callback interfaces at the lowest level possible so I can then use that interface entirely with a promise interface. In most cases, I use an automated way to promisify the interface such as util.promisify() or Bluebird's Promise.promisifyAll(). Then, my logic flow is entirely made out of promise-based operations and I'm never even faced with the type of code you show and any temptation to write code like that is gone too.

Some references:

Why does the Promise constructor need an executor?

Deferred Anti-Pattern

The Revealing Constructor Pattern

What's the correct pattern for Promise.defer?


If you really find that you think you "need" a Deferred object, then I'd suggest you encapsulate that functionality in a new object and use that, rather than manually coding it each time you use it. In all my coding, I've only ever found that code was easier to write and cleaner with a Deferred object and that was a pretty unusual system of a low level queue managing a bunch of a different tasks.

There are several examples of simple, short pieces of code to implement a Deferred object. Here's one:

Why does the Promise constructor need an executor?

like image 193
jfriend00 Avatar answered Dec 27 '22 19:12

jfriend00