Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session Proposal: NodeJS Asynchronous Scheduling #392

Closed
1 of 6 tasks
ronag opened this issue Feb 21, 2024 · 10 comments
Closed
1 of 6 tasks

Session Proposal: NodeJS Asynchronous Scheduling #392

ronag opened this issue Feb 21, 2024 · 10 comments
Assignees
Labels
Collaborator Summit London 2024 Collab Summit at Apr 3rd 2024 in London Session Proposal A session proposal for the Collaboration Summit

Comments

@ronag
Copy link

ronag commented Feb 21, 2024

Proposal

Topic of the session

Existing pitfalls in NodeJS asynchronous scheduling and patterns.

Type of the session

  • Collaborate
  • Workshop
  • Talk

Estimated duration of the session

Date and Time of the session

Level

  • Beginner
  • Intermediate
  • Advanced

Pre-requisite knowledge

Describe the session

Session facilitator(s), Github handle(s) and timezone(s)

Meeting notes and Virtual Meeting Link

Follow-up / Set-up sessions (if any)

Additional context (optional)


@ronag ronag added Bilbao 2023 Collab Summit at Sep 18th 2023 in Bilbao Collaborator Summit Session Proposal A session proposal for the Collaboration Summit labels Feb 21, 2024
@ronag
Copy link
Author

ronag commented Feb 21, 2024

@ronag ronag changed the title Session Proposal: NodeJS Zalgo and Promises Session Proposal: NodeJS Asynchronous Expectations Feb 21, 2024
@ronag ronag changed the title Session Proposal: NodeJS Asynchronous Expectations Session Proposal: NodeJS Asynchronous Scheduling Feb 21, 2024
@ronag
Copy link
Author

ronag commented Feb 21, 2024

@mcollina

@benjamingr
Copy link

Would love to attend this remotely or discuss offline

@ronag
Copy link
Author

ronag commented Feb 21, 2024

@ronag
Copy link
Author

ronag commented Feb 21, 2024

Example of problem to discuss.

import { EventEmitter } from 'events'

// Unhandled exception
setImmediate(async () => {
  const e = await new Promise(resolve => {
    const e = new EventEmitter()
    resolve(e)
    process.nextTick(() => {
      e.emit('error', new Error('nextTick'))
    })
  })
  e.on('error', () => {})
})

// Unhandled exception
setImmediate(async () => {
  const e = await new Promise(resolve => {
    const e = new EventEmitter()
    resolve(e)
    queueMicrotask(() => {
      e.emit('error', new Error('queueMicrotask'))
    })
  })
  e.on('error', () => {})
})

// OK, but slow
setImmediate(async () => {
  const e = await new Promise(resolve => {
    const e = new EventEmitter()
    resolve(e)
    setImmediate(() => {
      e.emit('error', new Error('setImmediate'))
    })
  })
  e.on('error', () => {})
})

Please don't get hung up the 'error' event. This is a problem with other events as well. In many cases they can be worse, e.g. a similar example with waiting for a 'ready' event would deadlock instead of crash.

@benjamingr
Copy link

I'm wondering if it's possible to solve generically by adding the capability to emit an event after someone is listening to it to the abstraction itself - e.g. by providing a helper to defer emitting an event up to setImmediate if there are no existing listeners for it.

This is similar to what we do for promises at the moment with unhanadled rejection detection.

@mcollina mcollina added London 2024 Collab Summit at Apr 3rd 2024 in London and removed Bilbao 2023 Collab Summit at Sep 18th 2023 in Bilbao labels Feb 25, 2024
@littledan
Copy link
Contributor

Can we toss into this topic, how are web platform APIs for managing asynchronicity like AbortController working for your needs in Node? Maybe that is too much of a tangent and can stick in the standards part, though.

@joyeecheung
Copy link
Collaborator

joyeecheung commented Mar 29, 2024

For the remote participants: we've scheduled Zoom Webinars for the sessions, please register using the links provided in #387 and you'll get a link in an email to join the sessions. More info in the issue mentioned.

EDIT: You can also get invited to be a panelist beforehand to save the registration & promotion step. Ping in the OpenJS slack with your email or send a email to the email in my GitHub profile to get an invitation.

@Qard
Copy link

Qard commented Apr 4, 2024

A possible solution is to allow EventEmitter to take a buffer strategy which can optionally buffer events to be flushed later. For example:

const events = require('events')

class EventEmitter extends events.EventEmitter {
  constructor(options) {
    super(options)
    this.buffer = options?.bufferStrategy || new NoBufferStrategy()
  }

  emit(name, ...args) {
    // No listeners yet. Try to buffer the event.
    if (!this.listeners(name).length && this.buffer.buffer(name, args)) {
      return
    }

    return super.emit(name, ...args)
  }

  on(name, ...args) {
    try {
      return super.on(name, ...args)
    } finally {
      // After attaching a handler, flush any buffered events.
      this.buffer.flush(name, (...args) => {
        this.emit(...args)
      })
    }
  }
}

// Default strategy does no buffering.
class NoBufferStrategy {
  buffer(name, task) {
    return false
  }

  flush() {}
}

// A possible alternative strategy could buffer the events until a handler is given.
class BufferStrategy {
  #buffered = new Map()

  buffer(name, args) {
    const map = this.#buffered

    if (!map.has(name)) {
      map.set(name, [])
    }
    map.get(name).push(args)

    return true
  }

  flush(name, handle) {
    const map = this.#buffered
    const queue = map.get(name)
    map.delete(name)

    for (const args of queue) {
      handle(name, ...args)
    }
  }
}

const p = new Promise((resolve) => {
  const e = new EventEmitter({
    bufferStrategy: new BufferStrategy()
  })

  process.nextTick(() => {
    e.emit('error', 'nextTick')
  })

  resolve(e)
})

p.then(e => {
  return new Promise(resolve => {
    setImmediate(resolve, e)
  })
}).then(e => {
  e.on('error', (error) => console.log('error', error))
})

It's worth noting that the BufferStrategy here is just very simplistic example to demonstrate the idea. It probably should not buffer indefinitely, it should have limits. At the least it should probably ensure error events don't just get held forever when there is no handler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Collaborator Summit London 2024 Collab Summit at Apr 3rd 2024 in London Session Proposal A session proposal for the Collaboration Summit
Projects
None yet
Development

No branches or pull requests

8 participants