Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/billiegoose/magic-portal
Pass objects with async methods between WebWorkers and the main thread easily
https://github.com/billiegoose/magic-portal
Last synced: 3 months ago
JSON representation
Pass objects with async methods between WebWorkers and the main thread easily
- Host: GitHub
- URL: https://github.com/billiegoose/magic-portal
- Owner: billiegoose
- License: mit
- Created: 2018-11-27T02:07:31.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2020-03-08T13:27:15.000Z (almost 5 years ago)
- Last Synced: 2024-10-13T11:36:29.387Z (3 months ago)
- Language: JavaScript
- Size: 7.81 KB
- Stars: 14
- Watchers: 3
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# magic-portal
Pass objects with async methods between WebWorkers and the main thread easily
## Installation
This is a [Node.js](https://nodejs.org/) module available through the
[npm registry](https://www.npmjs.com/). It can be installed using the
[`npm`](https://docs.npmjs.com/getting-started/installing-npm-packages-locally)
or
[`yarn`](https://yarnpkg.com/en/)
command line tools.```sh
npm install magic-portal --save
```## Usage
You load the library in both the main thread and the worker (same library, there's no client/server distinction)
and then create a `new MagicPortal(channel)` where `channel` is either the worker or the window.
Then you call `.set` and `.get` on it like it's a `Map`, except you have to `await` the `.get`.
The functions on the object HAVE to be async functions that return Promises.`index.html`
```html;(async () => {
let worker = new Worker("./worker.js")
const portal = new MagicPortal(worker)portal.set('main', {
alert: async (msg) => window.alert(msg)
})let adder = await portal.get('adder')
let result = await adder.add(2, 2)
console.log('2 + 2 =', result)})();
```
`worker.js`
```js
importScripts([
"https://unpkg.com/magic-portal/dist/index.umd.js"
])
;(async () => {const portal = new MagicPortal(self)
portal.set('adder', {
add: async (a, b) => a + b
})let main = await portal.get('main')
main.alert('hello from worker')
})();
```The same examples, but using ES modules
`index.html`
```htmlimport MagicPortal from "https://unpkg.com/magic-portal/dist/index.es6.js"
;(async () => {let worker = new Worker("./worker.js", {type: "module"})
const portal = new MagicPortal(worker)portal.set('main', {
alert: async (msg) => window.alert(msg)
})let adder = await portal.get('adder')
let result = await adder.add(2, 2)
console.log('2 + 2 =', result)})();
```
`worker.js`
```js
import MagicPortal from "https://unpkg.com/magic-portal/dist/index.es6.js"
;(async () => {const portal = new MagicPortal(self)
portal.set('adder', {
add: async (a, b) => a + b
})let main = await portal.get('main')
main.alert('hello from worker')
})();
```### Options
If you have some methods where you don't care about the return value, you can use the `void` option to tell MagicPortal you don't need to wait for the result.
This will cut the number of `postMessage` calls used in half, which could be useful if you have very high throughput (like an event emitter).```js
portal.set('main', {
add: async (a, b) => a + b,
alert: async (msg) => window.alert(msg)
}, {
void: ['alert']
})
```## How it works
Under the hood it uses a very simple `postMessage` remote procedure call (RPC) that looks like this:
```js
// Announce "I am able to start receiving messages"
{
type: "MP_INIT"
}
// Announce "I have this object"
{
type: "MP_SET",
object: "adder",
methods: ["adder"]
}
// Method call (request)
{
type: "MP_CALL",
object: "adder",
method: "add",
args: [2, 2],
id: 36
}
// Return value (response)
{
type: "MP_RETURN",
id: 36,
result: 4
}
// or Error
{
type: "MP_RETURN",
id: 36,
error: "this is the error message"
}
```Both sides of the MagicPortal queue their messages until they have received an `MP_INIT` message to account for the slight delay in starting up WebWorker threads.
Calling `.set` sends (or queues) an `MP_SET` message, and when you call `.get`, it returns a promise that is resolved once a corresponding `MP_SET` message is received.
(Therefore you should try to make all your `.set` calls before you start `await`ing for `.get` calls to avoid a mutual deadlock where both threads are `await`ing.)
The object returned by `.get` has methods that correspond to the `function` properties of the original object passed to `.set`.
Calling these methods sends an `MP_CALL` message with the arguments, and the MagicPortal on the other side of the channel will receive the `MP_CALL` message and call the original method with those arguments.
Therefore the function arguments have to be serializable by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) used by `postMessage` to send values.## Tests
```sh
npm install
npm test
```## Dependencies
None
## Dev Dependencies
- [babel-core](https://ghub.io/babel-core): Babel compiler core.
- [babel-preset-env](https://ghub.io/babel-preset-env): A Babel preset for each environment.
- [jest](https://ghub.io/jest): Delightful JavaScript Testing.
- [microbundle](https://ghub.io/microbundle): Zero-configuration bundler for tiny JS libs, powered by Rollup.## License
MIT