andywer / threads.js

๐Ÿงต Make web workers & worker threads as simple as a function call.
https://threads.js.org/
MIT License
3.06k stars 163 forks source link

Support (async) generator functions #251

Open kimamula opened 4 years ago

kimamula commented 4 years ago

I think it would be great if the observer pattern described here can be written as follows so that we do not have to depend on any observable libraries but ECMAScript standards.

// master.js
import { spawn, Thread, Worker } from "threads"

const counter = await spawn(new Worker("./workers/counter"))

for await (const newCount of counter()) {
  console.log(`Counter incremented to:`, newCount)
}
// workers/counter.js
import { expose } from "threads/worker"

function* startCounting() { // async generator function should also be OK
  for (let currentCount = 1; currentCount <= 10; currentCount++) {
    yield currentCount;
  }
}

expose(startCounting)
andywer commented 4 years ago

Hey @kimamula!

Thanks for bringing that up. I'd like to see this as two related topics here:

  1. Supporting async iterators as return values of functions:

    Makes sense ๐Ÿ‘

  2. Replacing observables with them:

    That's not gonna work that way as they serve roughly similar, but not the same use cases. Observables can actively raise an event and inform the subscriber about them at any time. Asynchronous iterators as returned by async generator functions, however, require the subscribing code the call them and might just not yield the event immediately. The subscriber needs to actively ask for new data, whereas the observable yields new data without any action of the subscribing code.

    So bottom line one is still a generator, even though async, so you tell it to generate a new value and after some time it will have done so, whereas the observable has a lifecycle of its own and will tell you when there's new data ๐Ÿ˜‰

kimamula commented 4 years ago

That makes sense and therefore passing value from a subscriber with next() should be possible in this pattern.

// master.js
import { spawn, Worker } from "threads"

const counter = (await spawn(new Worker("./workers/counter")))()

console.log(await counter.next()) // {value: 1, done: false}
console.log(await counter.next()) // {value: 2, done: false}
console.log(await counter.next()) // {value: 3, done: false}
console.log(await counter.next(true)) // {value: 1, done: false}
console.log(await counter.next()) // {value: 2, done: false}
console.log(await counter.next()) // {value: 3, done: false}
// ...
// workers/counter.js
import { expose } from "threads/worker"

function* startCounting() {
  for (let currentCount = 1; currentCount <= 10; currentCount++) {
    const reset = yield currentCount
    if (reset) {
      currentCount = 0
    }
  }
}

expose(startCounting)