First, I do get that not all uses of deferred-like objects really
merit the need for a deferred. For example, [here][1], I saved the
resolver and pulled the state out from the main closure to make the
state easier to follow. You could argue a deferred isn't really
necessary since I only care about the `resolve` function, and nothing
else. It's also pretty trivial to factor it back into a closure where
it was originally.

[1]: 
https://github.com/isiahmeadows/thallium/blob/master/lib/core/tests.js#L337-L428

But it's when external forces control them indirectly through a state
machine or similar, that's when it becomes necessary. I have some
closed-source uses, but here's a couple concrete examples I have in
OSS code:

1. Here, I have to treat it like a continuation because I have to wait
for an IPC protocol sequence to complete before it resolves/rejects:
https://github.com/isiahmeadows/invoke-parallel/blob/master/lib/api.js#L144-L147
2. Here, I have to treat it like a continuation because it's placed
into a job queue driven by mainly the completion of child processes:
https://github.com/isiahmeadows/website/blob/570db369cfca2b8a4a525be4e4621c854788b4d0/scripts/exec-limit.js#L71-L73

There is literally no other way to handle these beyond using a fake
deferred, thanks to the fact they aren't resolved directly in response
to any external forces, but indirectly as the result of a state
machine transition or similar. I can't even pass them around where I
need them, because there's a giant process wall I have to cross each
time. And it's this kind of use case that drove me to request this.
The resolver functions get in my way, they take up more memory than
necessary, and I've found myself occasionally adding separate arrays
of resolver/rejector functions so I can also avoid the indirection of
calling them.

In general, I don't like using deferreds if I can help it - it's
nothing but boilerplate for the common case. Here's what I usually
prefer in order, provided I can help it:

- The return value itself.
- `async`/`await`
- `Promise.prototype.finally` or some similar abstraction.
- `Promise.prototype.then`/`Promise.prototype.catch`
- `Promise.resolve`/`Promise.reject`
- `Promise.try` or some similar abstraction.
- `Promise.all([...])`/`Promise.race([...])
- `new Promise(...)` using the callbacks directly.
- `new Promise(...)`, converting the result to a pseudo-deferred.

I'm not asking about this because I *enjoy* deferreds - they're
nothing but useless boilerplate for the vast majority of use cases. In
fact, I actively try to avoid it most of the time. I'm just asking for
an escape hatch in case the *simple* stuff becomes boilerplate, one
mirroring how the spec already deals with those complex scenarios.
Very few things hit that breaking point when the callbacks become
boilerplate, but low-level async code requiring a dedicated state
machine driven by both calls and external effects has a habit of
hitting that very quickly.

-----

Isiah Meadows
m...@isiahmeadows.com
www.isiahmeadows.com


On Fri, Jul 20, 2018 at 12:04 AM, Bob Myers <r...@gol.com> wrote:
> I've used this pattern exactly twice in the large-scale app I'm working on
> now.
> One of those I was able to eliminate after I thought harder about the
> problem.
> The other I eventually replaced with the following kind of pattern:
>
> ```
> function createPromise(resolver, rejector) {
>   return new Promise((resolve, reject) {
>     resolver.then(resolve);
>     rejector.then(reject);
>     });
> }
> ```
>
> Obviously the way this works it that to create a promise "controllable" from
> "the outside",
> you create your own resolver and rejector promises to pass to
> `createPromise`,
> such that they trigger when you need them to.
> To put it a different way, instead of getting back and passing around
> deferred-like objects,
> which seems to be a massive anti-pattern to me,
> the client creates their own promise-controlling promises designed to
> trigger at the right time.
>
> Bob
>
> On Fri, Jul 20, 2018 at 9:07 AM Jordan Harband <ljh...@gmail.com> wrote:
>>
>> I don't think the Deferred pattern is a good primitive to have in the
>> language, and it's a pretty trivial primitive to write yourself if you need
>> it.
>>
>> On Thu, Jul 19, 2018 at 6:13 PM, Isiah Meadows <isiahmead...@gmail.com>
>> wrote:
>>>
>>> Sometimes, it's *very* convenient to have those `resolve`/`reject`
>>> functions as separate functions. However, when logic gets complex
>>> enough and you need to send them elsewhere, save a continuation, etc.,
>>> it'd be much more convenient to just have a capability object exposed
>>> more directly rather than go through the overhead and boilerplate of
>>> going through the constructor with all its callback stuff and
>>> everything.
>>>
>>> It's surprisingly not as uncommon as you'd expect for me to do this:
>>>
>>> ```js
>>> let resolve, reject
>>> let promise = new Promise((res, rej) => {
>>>     resolve = res
>>>     reject = rej
>>> })
>>> ```
>>>
>>> But doing this repeatedly gets *old*, especially when you've had to
>>> write it several dozen times already. And it comes up frequently when
>>> you're writing lower-level async utilities that require saving promise
>>> state and resolving it in a way that's decoupled from the promise
>>> itself.
>>>
>>> -----
>>>
>>> So here's what I propose:
>>>
>>> - `Promise.newCapability()` - This basically returns the result of
>>> [this][1], just wrapped in a suitable object whose prototype is
>>> %PromiseCapabilityPrototype% (internal, no direct constructor). It's
>>> subclass-safe, so you can do it with subclasses as appropriate, too.
>>> - `capability.resolve(value)` - This invokes the implicit resolver
>>> created for it, spec'd as [[Resolve]].
>>> - `capability.reject(value)` - This invokes the implicit rejector
>>> created for it, spec'd as [[Reject]].
>>> - `capability.promise` - This returns the newly created promise.
>>>
>>> Yes, this is effectively a deferred API, but revealing constructors
>>> are a bit too rigid and wasteful for some use cases.
>>>
>>> [1]: https://tc39.github.io/ecma262/#sec-newpromisecapability
>>>
>>> -----
>>>
>>> Isiah Meadows
>>> m...@isiahmeadows.com
>>> www.isiahmeadows.com
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss@mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss@mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to