Fair enough. I was just hoping that the internal mechanism itself
could be exposed, since the spec already has to use it (as do
implementations 99% of the time). In case you're concerned about the
potential for abuse, I do have this:

1. It's different than what you'd get with `new Promise(...)` itself.
This alone should push people away from it - you don't see a lot of
repeated `map.set("foo", foo).set("bar", bar)` when `new Map([["foo",
foo], ["bar", bar]])` is sufficient.
2. The name is relatively long and it sticks out very well - the
factory's name is about twice as long to type than `new Promise` and
it clearly doesn't return a Promise directly.
3. It's not a constructor, but a method. This makes it harder to
contort it into the traditional OOP madness, since methods aren't
subclassable.
4. The name clearly states it's creating a new capability, a new
"controller" of sorts for a promise. This is way more explicit than
`defer`, which sounds like you're "deferring" something (when you're
not), it'd would be much less prone to confusion and misuse.
5. Instead of the deferred type choosing how the promise is created,
the relationship is inverted. And since promise subclassing is a bit
annoying already, you're less likely to see the subclassing crap
happen around this.

I specifically *don't* want to make it simple, idiomatic, and easy to
use, because 1. it doesn't even properly capture errors that might
occur, and 2. it's a relatively unsafe API most won't really need.
However, it's easier to integrate into state-driven control flow, and
that's why I would like to see it added.

In case you're curious about other legitimate use cases:

- Simplifying returning a promise fulfilled when an event emits next,
or rejects if it's beat by an error event - if this is built-in, you
could drop most of the memory and computational overhead by just
dealing with the capability directly rather than invoking a closure.
- Returning a promise fulfilled when a socket completes, without the
overhead of an event emitter.
- Returning a promise fulfilled on next stream flush, rejected if an
error occurs - these are usually pooled, so the factory is a bit
wasteful with memory.
- Sending a worker thread a message and returning a promise fulfilled
with the response, rejected on error. When the message channel
contains unrelated messages as well, you can't use normal promises
with this.

Also, I created a quick prolyfill for my `Promise.newCapability` here:
https://gist.github.com/isiahmeadows/14ddaae1ae2e73df3115259a0aff1804

-----

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


On Sat, Jul 21, 2018 at 4:38 PM, Jordan Harband <ljh...@gmail.com> wrote:
> The use cases for "i want a resolved or rejected promise for a value/reason"
> are much more numerous than for "i want a deferred", so yes, I don't think a
> Deferred belongs in the language, and I think Promise.resolve/reject
> absolutely clear that bar.
>
> On Sat, Jul 21, 2018 at 11:19 AM, Isiah Meadows <isiahmead...@gmail.com>
> wrote:
>>
>> > I think what Jordan means, it's that the deferred has it use case, but
>> > probably we don't want it in Javascript native library. There's a lot of
>> > mature libraries implementing deferred wrappers and most of them are
>> > Promise
>> > like compatible, and even if you cannot use libraries or don't want to,
>> > you
>> > can easily implement a Promise extension and use it yourself.
>>
>> Jordan, is this accurate? It wasn't what I read of it, unless you sent
>> something that mistakenly missed the list.
>>
>> If that *is* the case, I don't see much precedent:
>> `Promise.resolve`/`Promise.reject` also hit that bar just as quickly,
>> if not *quicker*:
>>
>> ```js
>> // Exact polyfills, assuming `IsPromise` as per 25.6.1.6 is exposed
>> globally.
>> // https://tc39.github.io/ecma262/#sec-ispromise
>> Promise.resolve = Promise.resolve || function (value) {
>>     if (IsPromise(value) && value.constructor === Promise) return value
>>     let resolve
>>     let promise = new this((r, _) => { resolve = r })
>>     resolve(value)
>>     return promise
>> }
>>
>> Promise.reject = Promise.reject || function (value) {
>>     let reject
>>     let promise = new this((_, r) => { reject = r })
>>     reject(value)
>>     return promise
>> }
>> ```
>>
>> -----
>>
>> Isiah Meadows
>> m...@isiahmeadows.com
>> www.isiahmeadows.com
>>
>>
>> On Fri, Jul 20, 2018 at 12:15 PM, Augusto Moura
>> <augusto.borg...@gmail.com> wrote:
>> > I think what Jordan means, it's that the deferred has it use case, but
>> > probably we don't want it in Javascript native library. There's a lot of
>> > mature libraries implementing deferred wrappers and most of them are
>> > Promise
>> > like compatible, and even if you cannot use libraries or don't want to,
>> > you
>> > can easily implement a Promise extension and use it yourself.
>> >
>> > Interesting enough, I got a really weird case (reads contraintuitive,
>> > I'm
>> > pretty sure the semantics of the error are right) extending the Promise
>> > class to exemplify a simple Deferred implementation, the code:
>> >
>> > ``` js
>> > class Deferred extends Promise {
>> >   constructor(factory) {
>> >     super((resolve, reject) => {
>> >       Object.assign(this, { reject, resolve });
>> >       factory(resolve, reject);
>> >     });
>> >   }
>> > }
>> >
>> > const d = new Deferred(() => {});
>> > ```
>> > The problem is the usage of `this` before calling the super constructor
>> > (even when the using in the super call itself). I wonder with it there
>> > are
>> > any ways of capturing the super constructor arguments in a Base class
>> > using
>> > class syntax. You probably can get the arguments in a old "function
>> > class"
>> > syntax (can be done weakmaps too). We can probably start ~yet~ another
>> > thread on Promises, about this problem (supposing there's no way of
>> > passing
>> > `this` to the promise factory).
>> >
>> > Em sex, 20 de jul de 2018 às 03:03, Isiah Meadows
>> > <isiahmead...@gmail.com>
>> > escreveu:
>> >>
>> >> 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
>> >
>> > --
>> > Augusto Moura
>
>
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to