647

I was writing code that does something that looks like:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Someone told me this is called the "deferred antipattern" or the "Promise constructor antipattern" respectively, what's bad about this code and why is this called an antipattern?

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    or is having the `catch` block in the `getStuffDone` wrapper the antipattern? – The Dembinski Jan 06 '17 at 23:45
  • 3
    At least for the native `Promise` example you also have unnecessary function wrappers for the `.then` and `.catch` handlers (i.e. it could just be `.then(resolve).catch(reject)`.) A perfect storm of anti-patterns. – Noah Freitas Aug 30 '17 at 17:48
  • 11
    @NoahFreitas that code is written that way for didactic purposes. I wrote this question and answer in order to help people who run into this issue after reading a lot of code looking like that :) – Benjamin Gruenbaum Aug 30 '17 at 18:28
  • See also https://stackoverflow.com/questions/57661537/nested-extended-promises-seem-to-require-a-global-variable for how to eliminate not only explicit Promise construction, but the use of a global variable as well. – David Spector Aug 26 '19 at 21:01
  • 6
    What's up the odd side-by-side code examples? Never seen that here before, find it quite confusing. Had to check the revision history to understand that both are example of the same thing. – Alex Aug 05 '21 at 10:11
  • ah yes you forgot that promises are pseudomonads – Rainb Jan 31 '22 at 09:52
  • 1
    @Rainb more like this question isn't intended for people who likely don't yet know what monads are or that they chain. I'm sure that group included you at some point (as well as me) :) – Benjamin Gruenbaum Jan 31 '22 at 09:54

3 Answers3

448

The deferred antipattern (now explicit-construction anti-pattern) coined by Esailija is a common anti-pattern people who are new to promises make, I've made it myself when I first used promises. The problem with the above code is that is fails to utilize the fact that promises chain.

Promises can chain with .then and you can return promises directly. Your code in getStuffDone can be rewritten as:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Promises are all about making asynchronous code more readable and behave like synchronous code without hiding that fact. Promises represent an abstraction over a value of one time operation, they abstract the notion of a statement or expression in a programming language.

You should only use deferred objects when you are converting an API to promises and can't do it automatically, or when you're writing aggregation functions that are easier expressed this way.

Quoting Esailija:

This is the most common anti-pattern. It is easy to fall into this when you don't really understand promises and think of them as glorified event emitters or callback utility. Let's recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • @BenjaminGruenbaum: I'm confident in my use of deferreds for this, so no need for a new question. I just thought it was a use-case you were missing from your answer. What I'm doing seems more like the opposite of aggregation, doesn't it? – mhelvens Sep 25 '14 at 19:43
  • 2
    @mhelvens If you're manually splitting a non-callback API into a promise API that fits the "converting a callback API to promises" part. The antipattern is about wrapping a promise in another promise for no good reason, you're not wrapping a promise to begin with so it doesn't apply here. – Benjamin Gruenbaum Sep 25 '14 at 19:47
  • @BenjaminGruenbaum: Ah, I though deferreds themselves were considered an anti-pattern, what with bluebird deprecating them, and you mentioning "converting an API to promises" (which is also a case of not wrapping a promise to begin with). – mhelvens Sep 25 '14 at 20:05
  • @mhelvens I guess the _excess_ deferred anti pattern would be more accurate for what it actually does. Bluebird deprecated the `.defer()` api into the newer (and throw safe) promise constructor, it did not (in no way) deprecate the notion of constructing promises :) – Benjamin Gruenbaum Sep 25 '14 at 20:06
  • @BenjaminGruenbaum: No, of course they didn't deprecate the creation of new promises. But they deprecated the `.defer()` API, as you say. A bad move, in my opinion, since a '`defer`-like' API is more flexible, and clearly required for use-cases such as mine. --- But it's no real problem, of course. Their own documentation explains how express `defer()` in terms of the promise constructor. – mhelvens Sep 25 '14 at 20:14
  • One question : If I have two scenarios where, if the value exists return the value else make async call and get it. I achieved it by wrapping the whole thing in promise and resolve the promise with data else return the promise from aync call. Is there a better way? – Maverick Mar 07 '17 at 11:09
  • @Maverick you can `Promise.resolve(42)` inside `getStuffDone` to return existing value, or better yet use `Promise.resolve(getStuffDone(666)).then(...).catch(...)` instead of just `getStuffDone(666).then(...)` to make sure you work with a Promise even if `getStuffDone` is not cooperative enough (like throwing a `TypeError`) - see [You-Dont-Know-JS #trustable-promise](https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch3.md#trustable-promise) – Aprillion Apr 23 '17 at 17:44
  • 1
    Thank you @Roamer-1888 your reference helped me finally figuring out what was my issue. Looks like I was creating nested (unreturned) promises without realising it. – ghuroo May 30 '17 at 01:21
  • In your example, where would you write actual code for "get stuff done"... as is, getStuffDone is a useless function. – lowcrawler Jun 15 '22 at 16:41
  • @lowcrawler either before getStuffDone is called, as a parameter to it or if you need its result in a`.then` or after `await`ing it in an async function. – Benjamin Gruenbaum Jun 16 '22 at 17:29
  • If I understand correctly, nothing in `getStuffDone` function actually does anything and if I want something 'done' I need to do it before `getStuffDone` or after it's returned. 1) Why would we ever call that function instead of the `myPromiseFn` directly? 2) If plan to actually DO anything in a function, the `new Promise ... ` constructor isn't an anti-pattern? It's only an anti-pattern when all I'm doing is returning the results of a secondary async function? – lowcrawler Jun 16 '22 at 20:15
  • You do stuff before/after myPromiseFn to well... write code using it and make that code reusable by putting it in a function which is pretty common. As for `new Promise` - you avoid doing that as this answer shows by chaining the promise (`.then`) or `await`ing it. – Benjamin Gruenbaum Jun 18 '22 at 17:45
168

What's wrong with it?

But the pattern works!

Lucky you. Unfortunately, it probably doesn't, as you likely forgot some edge case. In more than half of the occurrences I've seen, the author has forgotten to take care of the error handler:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

If the other promise is rejected, this will happen unnoticed instead of being propagated to the new promise (where it would get handled) - and the new promise stays forever pending, which can induce leaks.

The same thing happens in the case that your callback code causes an error - e.g. when result doesn't have a property and an exception is thrown. That would go unhandled and leave the new promise unresolved.

In contrast, using .then() does automatically take care of both these scenarios, and rejects the new promise when an error happens:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

The deferred antipattern is not only cumbersome, but also error-prone. Using .then() for chaining is much safer.

But I've handled everything!

Really? Good. However, this will be pretty detailed and copious, especially if you use a promise library that supports other features like cancellation or message passing. Or maybe it will in the future, or you want to swap your library against a better one? You won't want to rewrite your code for that.

The libraries' methods (then) do not only natively support all the features, they also might have certain optimisations in place. Using them will likely make your code faster, or at least allow to be optimised by future revisions of the library.

How do I avoid it?

So whenever you find yourself manually creating a Promise or Deferred and already existing promises are involved, check the library API first. The Deferred antipattern is often applied by people who see promises [only] as an observer pattern - but promises are more than callbacks: they are supposed to be composable. Every decent library has lots of easy-to-use functions for the composition of promises in every thinkable manner, taking care of all the low-level stuff you don't want to deal with.

If you have found a need to compose some promises in a new way that is not supported by an existing helper function, writing your own function with unavoidable Deferreds should be your last option. Consider switching to a more featureful library, and/or file a bug against your current library. Its maintainer should be able to derive the composition from existing functions, implement a new helper function for you and/or help to identify the edge cases that need to be handled.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Are there examples , other than a function including `setTimeout` , where constructor could be used but not be considered "Promise constructor anitpattern" ? – guest271314 Nov 23 '15 at 21:39
  • 3
    @guest271314: Everything asynchronous that doesn't return a promise. Though often enough you get better results with the libraries' dedicated promisification helpers. And make sure to always promisify at the lowest level, so it's not "*a function including `setTimeout`*", but "*the function `setTimeout` itself*". – Bergi Nov 23 '15 at 22:13
  • @guest271314 A function that just includes a call to `setTimeout` is clearly different from *the function `setTimeout` itself*, isn't it? – Bergi Nov 23 '15 at 22:21
  • Perhaps not aware of clear difference , here. `function () {setTimeout(dostuff, duration)}` , `setTimeout(dostuff, duration)` ? Can describe context difference between two , if above two variations are , or are not , accurate representation of _" call to setTimeout is clearly different from the function setTimeout"_ ? – guest271314 Nov 23 '15 at 22:26
  • @guest271314: The lowest level to be promisified is the `setTimeout` function. Your `doStuff` should go in a promise callback. – Bergi Nov 23 '15 at 22:33
  • I can't find any `Promise.prototype.done` in the spec. Were you referring to the non-standard https://www.promisejs.org/api/#Promise_prototype_done, or did you mean `then`? – Oriol Aug 29 '16 at 01:46
  • @Oriol: Yes, `done` of Q or jQuery or whatever. Doesn't really matter since its the anti-example anyway, but I'll swap it for the standard one (given that I also use the promise constructor there) – Bergi Aug 29 '16 at 01:48
  • 7
    I think one of the important lessons here, one that has not been clearly stated so far, is that a Promise and its chained 'then' represents one asynchronous operation: the initial operation is in the Promise constructor and the eventual endpoint is in the 'then' function. So if you have a sync operation followed by an asynch operation, put the sync stuff in the Promise. If you have an async operation followed by a sync, put the sync stuff in the 'then'. In the first case, return the original Promise. In the second case, return the Promise/then chain (which is also a Promise). – David Spector Aug 27 '19 at 00:44
  • If our API expects some promise-based `task` from a caller and should return to them a promise that represents this `task` result and we **can't** run this `task` immediately for some reasons, we are forced to use this antipattern, aren't we? – Ilya Loskutov Jun 14 '21 at 19:01
  • @Mergasov Not sure what you mean by "*we can't run this task immediately*", but [whatever you are waiting on, promisify that and chain onto it](https://stackoverflow.com/a/37426491/1048572). No reason to use the antipattern. You might want to [ask a new question](https://stackoverflow.com/questions/ask) where you can share the relevant parts of your code. – Bergi Jun 14 '21 at 19:19
30

Now 7 years later there is a simpler answer to this question:

How do I avoid the explicit constructor antipattern?

Use async functions, then await every Promise!

Instead of manually constructing nested Promise chains such as this one:

function promised() {
  return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
      getAnotherPromise(result).then(function(result2) {
        resolve(result2);
      });
    });
  });
}

just turn your function async and use the await keyword to stop execution of the function until the Promise resolves:

async function promised() {
   const result =  await getOtherPromise();
   const result2 = await getAnotherPromise(result);
   return result2;
}

This has various benefits:

  • Calling the async function always returns a Promise, which resolves with the returned value and rejects if an error get's thrown inside the async function
  • If an awaited Promise rejects, the error get's thrown inside the async function, so you can just try { ... } catch(error) { ... } it like the synchronous errors.
  • You can await inside loops and if branches, making most of the Promise chain logic trivial
  • Although async functions behave mostly like chains of Promises, they are way easier to read (and easier to reason about)

How can I await a callback?

If the callback only calls back once, and the API you are calling does not provide a Promise already (most of them do!) this is the only reason to use a Promise constructor:

 // Create a wrapper around the "old" function taking a callback, passing the 'resolve' function as callback
 const delay = time => new Promise((resolve, reject) =>
   setTimeout(resolve, time)
 ); 

 await delay(1000);

If await stops execution, does calling an async function return the result directly?

No. If you call an async function, a Promise gets always returned. You can then await that Promise too inside an async function. You cannot wait for the result inside of a synchronous function (you would have to call .then and attach a callback).

Conceptually, synchronous functions always run to completion in one job, while async functions run synchronously till they reach an await, then they continue in another job.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 1
    The whole point of using an async function is so you *don't* stop execution.... – john k Jun 21 '21 at 14:47
  • 7
    @john there is a huge difference between "stops execution of the async function" and "stops execution". – Jonas Wilms Jun 21 '21 at 16:16
  • 3
    Even better: use async/await, and add eslint rules `no-floating-promises` and `no-misused-promises`. If you forget to `await` a Promise, the linter will yell at you. – Coderer Oct 22 '21 at 15:27
  • *"stop execution of ..."* is confusing... the truth is that the execution *returns* from the function call. – trincot May 27 '22 at 13:55
  • @trincot that's only true for the first await. I'm open to suggestions though to word this better. The [spec](https://262.ecma-international.org/12.0/#await) says "restore the execution context that is at the top of the execution context stack" which could be the settlement of a previously awaited promise (so I guess this is even more confusing). – Jonas Wilms May 27 '22 at 14:43
  • Indeed, it is true for the first await, but it explains well that the caller gets a promise and JS execution continues as you would expect from a return statement. One could even say it is true for every await, with the difference that the other times the *caller* is the event loop process (which resumed the function execution context), and not JS code. – trincot May 27 '22 at 14:49
  • @trincot there is no such thing as an event loop in JS. I'd like to keep my answers close to the spec ... – Jonas Wilms May 27 '22 at 15:05
  • The host environment is responsible for the asynchronous callback system referred to in the spec as jobs and job enqueuing. – trincot May 27 '22 at 15:15