5

I'm using TypeScript and async/await to represent an asynchronous workflow. Part of that workflow is to call out to a web worker and continue when it calls back with a result.

In C#, I'd create a TaskCompletionSource, await its Task and elsewhere in the code would call SetResult to resolve the TaskCompletionSource.

I can do the same thing in JavaScript by initializing a Deferrer object using Promise.defer(), awaiting its Promise and elsewhere, in the window.onmessage listener would call the resolve (or reject) method to let the asynchronous workflow continue.

Sounds lovely, but MDN says defer is obsolete. The proposed solution of using the Promise constructor which takes the delegate doing the work and calling the resolve/reject methods doesn't work for me, because that logic could be out of my reach, I just want on object to call resolve or reject on in an entirely different lexical scope, I can't do that with that function.

There's a Backwards and forwards compatible helper that gives me such object by binding the resolve and reject functions that I can use without chaning the semantics of my code. But is this a bad practice? Is there a recognized, better pattern? What's an idiomatic equivalent of TaskCompletionSource in JavaScript?

Tomáš Hübelbauer
  • 9,179
  • 14
  • 63
  • 125

1 Answers1

5

There is absolutely nothing wrong with the helper that makes a deferred as long as you are sure you really need a deferred. In all my promise programming, I've only found one situation where I actually needed a deferred and couldn't just restructure my code to use the typical Promise constructor pattern.

It seems the situation I found and the one others have pointed to occurs when you have a separate between the code that creates the promise/deferred object and the code that resolves or rejects it. Usually, you have those together, but there are sometimes when they are completely different sections of code and there are logical reasons for doing it that way. In those cases, a deferred object built off a regular promise can be the cleaner method of implementation.

Here's another simple Deferred implementation that is also backwards and forwards compatible:

Why does the Promise constructor need an executor?

But is this a bad practice?

I know there are some purists out there that think you should never do this. But, I'd say that it's a perfectly fine practice if you are sure that it is the cleanest and simplest way to implement your code and that implementing it with the typical Promise constructor executor callback just isn't as practical. There are those who want to use a deferred without even trying or learning the promise constructor/executor and that would not be a good practice. For a variety of reasons, the promise constructor should be used when practical.

One big reason that the promise constructor is preferred is that all the promise-specific code in the executor callback function is "throw-safe". If an exception is thrown in that code, it will automatically be caught and reject the promise (which is a good thing). Once you use a deferred and have a bunch of code that manipulates the promise outside of that callback, it is not automatically throw-safe. In fact, your code can even throw synchronously which is a nightmare for callers to protect against. You can see a description of that issue here: https://stackoverflow.com/a/37657831/816620. So, the deferred pattern is not preferred, but with proper protections, there are still some cases (generally rare) where it leads to a cleaner implementation.

Is there a recognized, better pattern?

Pretty much the same answer as above. The preference is to use the promise constructor/executor, but in situations where that isn't practical (usually where different sections of code create the promise and then resolve/reject it) and there's no simple way to reorganize the code to work well with the promise constructor/executor, then a deferred object is likely the preferred implementation. You should generally find those situations rare because most of the time you can structure your code so the same chunk of code is creating and resolve/rejecting the promise and you don't need a deferred.

What's an idiomatic equivalent of TaskCompletionSource in JavaScript?

There is no built-in equivalent in Javascript. As such, you'd have to build your own and it seems like promises are a natural way to do so since they are naturally built to signal completion or error. You'd have to show us your actual code for us to offer an opinion on whether your specific situation can be implemented cleanly without using a deferred object.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    This is an amazing answer, thank you for putting so much time into it. I can't imagine how I would create a `Promise` to wait on in the main window and `resolve` it from a *web worker* (a different file no less!) while using the `Promise` constructor executor, so I think I'm fine using `Deferred` for this. – Tomáš Hübelbauer Aug 02 '16 at 05:21
  • @TomášHübelbauer - Well, the webWorker is ultimately going to have to send a message to the main thread to communicate with it, right? Could you have the promise executor listening for that message and have it resolve the promise in the executor when it gets the message? I'm just guessing here because I haven't seen your real code. – jfriend00 Aug 02 '16 at 05:23
  • Come to think of it, I could listen to the message in the executor implementation, though currently my message processor is in a central place and this way I'd have to shatter it to each executor. I will play around with it, maybe I am overlooking something. Will post a question if I'm out of ideas or unsure. :) – Tomáš Hübelbauer Aug 02 '16 at 05:26
  • 1
    @TomášHübelbauer - Often (though not always) the executor design pattern just requires a rethink about how the code is structured in order to make use of it. Good luck. – jfriend00 Aug 02 '16 at 05:29
  • I have always avoided it in because I was creating a `callback` function in every function. jshint warns not to create functions dynamically. Is this not a big performance deal? Is that why its perferred to just use `new Promise(callback)`? – Noitidart Aug 19 '16 at 01:13
  • @Noitidart - In my opinion, jshint is causing you to avoid the better way to code. These code checkers are actually pretty dumb. If you blindly do what they say, you will be moving away from best practices in some cases. You have to know what you're doing and when to ignore them. Anonymous inline functions are incredibly useful in Javascript and clearly the best way to write some code. I would not avoid them. jsHint is probably only complaining if you're doing that in a loop in which case I'd have to see the exact code to know what I'd recommend. – jfriend00 Aug 19 '16 at 01:20