Quicker Worker
A library for quickly using web worker in browser. It is not designed for node side, just for browser side.
Install
npm install quicker-worker
or
<script src="dist/quicker-worker.js"></script>
Usage
If you use module import:
import {create, run} from 'quicker-worker'
let $worker = create(`function(n) {
n ++
return n
}`)
$worker.invoke(12).todo(n => {
console.log(n)
worker.close()
})
If you use global link:
let create = window.QuickerWorker.create
let run = window.QuickerWorker.run
Only two functions create
and run
provided. So it is easy to use.
API
Now let's talk about these two functions.
create(factory)
This function create a worker instance. So after you created an instance, you will use it for more operation.
let $worker = create(`function(p1, p2) {
// ...
}`)
factory
The function to run in worker thread when you invoke. It should better to be a sting, which is like a function. However, you can directly pass a function if you have not special variables in it (a function will be transformed to be a string when concated with a string). So the following two are both ok:
Use string:
let $worker = create(`function(p1, p2) {
// ... you can use $xhr here if you set `xhr` in `options` to be true
}`)
Use function:
let $worker = create(function(p1, p2) {
// ... don't use $xhr here
})
And you'd better to know that, program in worker thread use the different memory context, so your main js thread is not sharing variables with worker thread. So, do not using variables which is in main js script in factory function. One word, treat factory function as a single scope.
And you should know more about web worker program: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers.
$worker
An instance will be created after you use create
. Let's call it $worker
. So $worker will have 2 special properties more than a normal worker instance: invoke, close.
$worker.invoke(data).todo(data => {}).catch(e => {})
Use this to run the factory you have passed into create
. Every time you run invoke, the factory will be run once.
data:
It is params passed into factory. For example:
let $worker = create(`function(p1, p2) {
return $xhr.post('your_url', {p1, p2})
}`)
$worker.invoke(['name', 'age']).todo(data => {
console.log(data)
})
You give p1 and p2 as params of factory, and when you run invoke, you should pass an array which will be used as fn.apply
's second parameter.
If your factory has only one parameter, you can just pass one data, not need to pass an array:
let $worker = create(`function(data) {
return $xhr.post('your_url', data)
}`)
$worker.invoke({name: 'tom'}).todo(data => {
console.log(data)
})
I mentioned $xhr
in previous code. $xhr
and $notify
is available in your factory program. I will talk about them later.
return value in factory:
You should return a value or a promise in factory. If you do not return anything, todo
will recieve nothing. However, if you use $notify
in factory, it is ok to return nothing, $notify data will be recieved by todo.
After you run invoke, the factory function will be run, and the return value or promise or $notify data will be passed into todo
:
let $worker = create(`function(data) {
data.forEach(item => ...)
return data
}`)
$worker.invoke([...]).todo(data => {
console.log(data)
})
When you return a promise in factory, promise.then data will be send back. So if you return a promise in factory, you can directly use promise success data in todo
callback.
let $worker = create(`function(data) {
return new Promise(resolve => {
// ...
resolve(data)
})
}`)
$worker.invoke([...]).todo(data => { // use data directly in todo
console.log(data)
})
todo(callback):
Here callback is a function which has one parameter.
After the factory function is run in worker thread, and postMessage
back to main js thread, callback
will be run.
However, if you use $notify
in factory, callback
will be run immediately when $notify is called.
callback
is a function like:
$worker.todo(data => {})
It has a parameter which is the data returned or notified by factory.
todo
can invoke multiple times, but only one callback function will be used. The last todo callback function will be use. Let's look into an example:
$worker.invoke(12) // invoke factory function, if you do not add todo, nothing will happen when function is finished
.todo(data => console.log(1))
.todo(data => console.log(2))
In the above example, 2 will always be show, because the last todo callback function will be the final function to use.
Notice: there is a special case, when you invoke factory, it is too quickly to finish the function so that the result is there, when this happen, the first todo callback will be run at first. In fact, it is not recommanded to run a small program in worker thread.
catch(callback):
catch
is for error in worker thread. When a error is threw in worker thread, catch callback will run.
The error info will be passed into callback:
$worker.catch(e => console.log(e))
$worker.close()
Do NOT use $worker.terminate()
to close worker thread, because we need to clean the context. $worker.terminate()
only close the thread, but some other resource are not cleaned.
And do not close a worker instance after it created, you should close it after invoke success. For example (a bad example):
let $worker = create(`function(data) {
return data
}`)
$worker.invoke({name: 'tom'}).todo(data => console.log(data))
$worker.close() // never do like this
The right way:
$worker.invoke({name: 'tom'})
.todo(data => {
console.log(data)
worker.close() // close worker after you get the result
})
run(factory, args)
Sometimes you do not need to create a worker instance, you just want to run a program in worker thread once. For example, you want to caculate a big data in worker:
run(`function(data) {
// caculate data
return data
}`, [..big data..])
.then(data => {
// use caculate result
})
run
function return a Promise, and caculated result is passed into then
.
You do not need to close it, it has been closed inside. And params rule is the same with invoke.
In fact, run is a brief packaging of create, invoke, todo and close.
global vars
There are two global vars can be used in factory function. They are $xhr
and $notify
.
$xhr
To provide a XMLHttpRequest base api in your factory by using $xhr
.
import {create} from 'quicker-worker'
import {createXHR} from 'quicker-worker/dist/xhr'
let $worker = create(`function(p1, p2) {
${createXHR()}
return $xhr.post('your_url', {p1, p2})
}`)
$worker.invoke(['name', 'age']).then(data => {
console.log(data)
})
Notice: you should use createXHR()
, not createXHR
.
Here, as you see, you can use $xhr directly in factory, and return the request as a Promise instance in factory. In $worker.invoke
we have talked about return Promise in factory.
Now, let's talk about the detail of $xhr
.
$xhr({
url: '',
type: 'GET',
headers: {},
timeout: 20000,
async: true,
before: function() {}, // which will be run after XMLHttpRequest instance created before timeout set
beforeOpen: function() {}, // which will be run before XMLHttpRequest instance open
beforeSend: function() {}, // which will be run before request sent
}).then(data => ...).catch(e => ...)
It is like jQuery $.ajax. And it provides .get
and .post
methods:
$xhr.get('url').then(...)
$xhr.post('url', postData).then(...)
url make sense! Because XMLHttpRequest in worker thread is not share context with main thread, so requests in worker will face cross-domain
problem. It does only allow to request to current domain host, for example, you can not request for yourdomain.com to otherdomain.com. And even you can not visit with url like '/root/to/path', because there is no window.location in worker thread. Thought I have set withCredentials
to be true, it seems not working.
To fix this problem, I have inject current host into worker program, so that you can pass a absolute url path like '/root/to/path' which will be treated as local url. So don't be worry about url, you can and must use absolute url to send request.
In fact, I recommand you to use fetch
api directly in worker code, which is more easy.
$notify && $throw
To invoke todo
callback function more free, I provide $notify
and $throw
functions so that you can post data back to main thread anytime. $notify
means send back successful data, $throw
means send back error.
let $worker = create(`function() {
//..
if (wrong) {
$throw(err)
return
}
$notify(1)
return 2
}`)
$worker.invoke().todo(data => console.log(data)) // data will be 1
If you use $notify
or $throw
, return value in factory will be ignored. $notify
or $throw
has higher priority.
Think about a situation: make ajax in a worker to request latest data cyclicly, if data is changed, update your view. This is a good case to use $notify
:
let $worker = create(`function() {
setInterval(function() {
// do something
$notify(data)
}, 60*1000)
}`)
$worker.invoke().todo(data => { ... })
In this case, todo callback will be called every 1 minute.
Others
After you create a worker thread, the function will not run until you call invoke
. So do not invoke a worker thread factory which has setInterval
too many times unless you know what you are doing.
When you post back data from worker thread to main thread, you'd better to remember that never send back an instance of a class. i.e.
// in worker factory
var e = new Error(msg)
$notify(e) // this is wrong
The previous code will throw an error because memory is not shared between different threads.
quicker-worker use blob to inject js, which make quicker-worker not work in IE10. So if you want to support IE10, you should have to research another library until I want to improve this library someday.
quicker-worker use native Promise, so that you can not use it in some environment which is not supporting native Promise until I want to improve this library someday.
If you want some referer, you can look into https://github.com/zhangyuanwei/EasyWorker which has more feature and supports node side.