/event-worker

A simpler way of dealing with Web Workers

Primary LanguageJavaScriptMIT LicenseMIT

event-worker

Minimalistic event/promified driven web worker abstraction.

Build Status npm version JavaScript Style Guide

Install

npm

npm install event-worker --save

CDN

https://cdn.jsdelivr.net/npm/event-worker@1.3.0/index.min.js

Usage

Basic example

In your main thread (main.js):

const EventWorker = require('event-worker')

const worker = new EventWorker('path/to/my/worker.js')

async function test(){
  const user = await worker.emit('getUserById', { id: '30242' })

  /*
  {
    id: '30242',
    name: 'neil',
    lastname: 'tyson degrasse'
  }
  */
//...

And then in your web worker (worker.js) you can listen for that event and respond back with the requested data:

const EventWorker = require('event-worker')

const worker = new EventWorker()

worker.on('getUserById', async ({payload}) => {

  let user = await getUser(payload.id)

  return user // Respond back to the main thread with the data requested.

})


async function getUser(id){

  let user = await fetchUserFromLocalDatabase(id)

  if(user) return user

  user = await fetchUserFromServer(id)

  saveUserInLocaDatabase(user)

  return user
}

Workload splitting

If you want to keep your main thread running smoothly dividing the work load of expensive computational task between multiple web workers becomes easier.

From main thread (main.js):

const EventWorker = require('event-worker')

const workerPath = 'path/to/my/worker.js'

const workerPool = [
  new EventWorker(workerPath),
  new EventWorker(workerPath),
  new EventWorker(workerPath)
]

const sum = (a, b) => a + b

const multiplyBy2InOtherThread = (worker, index) => worker.emit('multiply_by_2', index)

(async ()=>
  (await Promise.all(
    workerPool.map(multiplyBy2InOtherThread)
  )).reduce(sum, 0)
)() // 6

From worker (worker.js):

importScripts('path/to/source/event-worker.js')

const worker = new EventWorker()

worker.on('multiply_by_2', ({payload}) => payload * 2 )

Bidirectional communication

You can listen for events triggered by your workers.

From main thread (main.js):

//...

worker.on('interestingData', ({payload})=>{

  doSomethingWithInterestingData(payload)

  return 'Good job worker!'

})

//..

From worker (worker.js):

//...
const res = await worker.emit('interestingData', 'interestingString')

res // => 'Good job worker!!'

Inlining code

Instead of having a separate file for your worker, you can wrap your code inside a function and pass it as an argument to the constructor of EventWorker. This is a good option when prototyping.

From main (main.js):

const worker = new EventWorker(async (mainThread) => {

  let res = await mainThread.emit('sayingHiFromWorker', 'Hi main thread!')

  console.log(res) // Hello worker!

})

worker.on('sayingHiFromWorker', ({payload}) => {

  console.log(payload) // Hi main thread!

  return "Hello worker!"

})

Caveat

When you inline functions it is easy to get confused by the execution context. If you try to access a variable that is outside the scope of the inline function it will fail.

const favoriteAnimal = 'chiguire'

const worker = new EventWorker(async (mainThread) => {
  // This will get executed in a worker.
  mainThread.on('onGetAnimals', ()=>{

    console.log(favoriteAnimal) // fails. favoriteAnimal variable is not in the same execution context.

    //...
  })

})

Error Handling

Error handling works the same as you would expect from a promise executed in the same thread:

From main thread (main.js):

const EventWorker = require('event-worker')

const worker = new EventWorker('path/to/my/worker.js')

worker.emit('rejectThisCall')
  .catch((reason) => {
    console.log(`Rejected because: "${reason}" `)
  })

From worker (worker.js):

importScripts('path/to/source/event-worker.js')

const worker = new EventWorker()

//throwing errors
worker.on('rejectThisCall', () => {
  throw new Error()
})

// throwing async errors
worker.on('rejectThisCallAsync', async ()=> {
  throw new Error()
})

Instead of embedding event-worker into your worker file with a module bundler, you can use the built in function importScripts:

importScripts('path/to/source/event-worker.js')

const worker = new EventWorker()

// ...

EventWorker reference is injected into the global scope once it's loaded.

API

new EventWorker(source) EventWorker

Creates a new instance

  • source string | function | undefined

    • If a string is passed: It will assume it is the worker source file path.

    • If a function is passed it will get converted into a string an then transformed into a worker.

    • If nothing (undefined) is passed it will assume that the environment is the worker.

emit(eventName, data) Promise

Emits a event.

  • eventName String

  • data Any

on(eventName, callback) EventWorker

Registers for an event.

  • eventName

  • callback function(object) => Promise<any>

    Gets executed when eventName is emited.

    • object
      • object.payload any

        Data sent from the event emitter to the listener.

terminate() void

Immediately terminates the Worker. This does not offer the worker an opportunity to finish its operations; it is simply stopped at once.

Changelog

2017-11-04

  • Removed "resolve" and "reject" function properties in the "on" callback.

Contributing

All contributions are welcome.

License

MIT © Alvaro Bernal