Reputation: 10657
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()
, await
ing 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?
Upvotes: 5
Views: 2324
Reputation: 707976
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.
Upvotes: 6