vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
208.05k stars 33.69k forks source link

Allow the event emitter to know when all subscribers have voted to have finished #11048

Closed Abuntxa closed 4 years ago

Abuntxa commented 4 years ago

What problem does this feature solve?

I am trying to develop a cancelable event.

Typically, this is implemented by emitting an intent event before an action. This event normally contains an argument passed by reference with the final voting result "{ cancel: false }".

If the event handler requires some kind of long validation in order to give the final vote (normally making use of promises), the emitter loses the chance of getting this result as the $emit function does not return any promise.

I know that there are alternatives such as passing an array of promises in the cancelable argument itself. This way subcribers could push their promises in the argument passed by reference. And the emitter could receive all promises and wait for all of them to have concluded.

But it just seems a little hacky for something that looks quite usual.

Why should the subscriber have to adapt its implementation for an emmiter requirement?

Furthermore, why the event subscriber cannot make use naturally of the async functions ?

What does the proposed API look like?

Ideally I would change the default $emmit implementation in order to return a Promise; this promise would be resolved when all the promises returned by all the subscribers are over.

Currently the $emit implementation returns the component itself (for use cases I don't know) and therefore this would be a breaking change.

That's why I'm proposing a new operation for emmiting the event ($emitP).

Vue.prototype.$emitP = function event: string): Promise<void> {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component `  
          `${formatComponentName(vm)} but the handler is registered for "${event}". `  
          `Note that HTML attributes are case-insensitive and you cannot use `  
          `v-on to listen to camelCase events when using in-DOM templates. `  
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    const promises = []
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i  ) {
       const res = invokeWithErrorHandling(cbs[i], vm, args, vm, info)
       if (isPromise(res)) {
        // We avoid breaking the notifications in case of subscriber error
        promises.push(res.catch(() => undefined))
       }
      }
    }
    // We hide the results
    return Promise.all(promises).then(()=>{})
  }
Justineo commented 4 years ago

See #11049.