Skip to content

Commit e2cad6d

Browse files
author
Andy Wermke
committed
Add support for async thread functions
1 parent 3a726ea commit e2cad6d

File tree

5 files changed

+89
-17
lines changed

5 files changed

+89
-17
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# threads.js - Changelog
22

3+
## Next release
4+
5+
- Support for async thread functions
6+
37
## 0.7.3
48

59
- Trigger worker error event on unhandled promise rejection in worker [#49](https://github.com/andywer/threads.js/issues/49)

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,29 @@ module.exports = function(input, done) {
108108
};
109109
```
110110

111+
### Async functions
112+
113+
You can also pass async functions, a.k.a. functions returning a Promise, to spawn threads.
114+
115+
```javascript
116+
const spawn = require('threads').spawn;
117+
118+
const thread = spawn(function ([a, b]) {
119+
// Remember that this function will be run in another execution context.
120+
return new Promise(resolve => {
121+
setTimeout(() => resolve(a + b), 1000)
122+
})
123+
});
124+
125+
thread
126+
.send([ 9, 12 ])
127+
// The handlers come here: (none of them is mandatory)
128+
.on('message', function(response) {
129+
console.log('9 + 12 = ', response);
130+
thread.kill();
131+
});
132+
```
133+
111134

112135
### Thread Pool
113136

src/worker.browser/slave.js.txt

+20-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ function handlerProgress(progress) {
1616
this.postMessage({ progress : progress });
1717
}
1818

19+
function handlerError(error) {
20+
// Need to clone error manually to avoid DataCloneError, since errors cannot be send
21+
var cloned = {
22+
message: error.message,
23+
name: error.name,
24+
stack: error.stack
25+
};
26+
this.postMessage({ error : cloned });
27+
}
28+
1929
function handlerDoneTransfer() {
2030
var args = Array.prototype.slice.call(arguments);
2131
var lastArg = args.pop();
@@ -27,6 +37,10 @@ function handlerDoneTransfer() {
2737
this.postMessage({ response : args }, lastArg);
2838
}
2939

40+
function isPromise (thing) {
41+
return thing && typeof thing.then === 'function';
42+
}
43+
3044
self.onmessage = function (event) {
3145
var scripts = event.data.scripts;
3246
if (scripts && scripts.length > 0 && typeof importScripts !== 'function') {
@@ -48,13 +62,18 @@ self.onmessage = function (event) {
4862

4963
if (event.data.doRun) {
5064
var handler = this.module.exports;
65+
5166
if (typeof handler !== 'function') {
5267
throw new Error('Cannot run thread logic. No handler has been exported.');
5368
}
5469

5570
var preparedHandlerDone = handlerDone.bind(this);
5671
preparedHandlerDone.transfer = handlerDoneTransfer.bind(this);
5772

58-
handler.call(this, event.data.param, preparedHandlerDone, handlerProgress.bind(this));
73+
var returned = handler.call(this, event.data.param, preparedHandlerDone, handlerProgress.bind(this));
74+
75+
if (isPromise(returned)) {
76+
returned.then(preparedHandlerDone, handlerError.bind(this));
77+
}
5978
}
6079
}.bind(self);

src/worker.node/slave.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ function messageHandlerError(error) {
5353
});
5454
}
5555

56+
function isPromise (thing) {
57+
return thing && typeof thing.then === 'function';
58+
}
59+
5660
process.on('message', function(data) {
5761
if (data.initByScript) {
5862
messageHandler = require(data.script);
@@ -67,6 +71,13 @@ process.on('message', function(data) {
6771
// so initialization errors will be printed to console
6872
setupErrorCatcher();
6973

70-
messageHandler(data.param, messageHandlerDone, messageHandlerProgress);
74+
const returned = messageHandler(data.param, messageHandlerDone, messageHandlerProgress);
75+
76+
if (isPromise(returned)) {
77+
returned.then(
78+
(result) => messageHandlerDone(result),
79+
(error) => messageHandlerError(error)
80+
);
81+
}
7182
}
7283
});

test/spec/worker.spec.js

+30-15
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import sinon from 'sinon';
44
import Worker from '../../lib/worker';
55
import { config, spawn } from '../../';
66

7-
87
const env = typeof window === 'object' ? 'browser' : 'node';
98

109
function echoThread(param, done) {
@@ -39,7 +38,6 @@ function expectEqualBuffers(buffer1, buffer2) {
3938
}
4039
}
4140

42-
4341
describe('Worker', function () {
4442

4543
this.timeout(4000);
@@ -55,7 +53,6 @@ describe('Worker', function () {
5553
});
5654
});
5755

58-
5956
it('can be spawned', () => {
6057
const worker = spawn();
6158

@@ -173,6 +170,8 @@ describe('Worker', function () {
173170
it('can update progress', done => {
174171
const progressUpdates = [];
175172
const worker = spawn(progressThread);
173+
let messageHandlerInvoked = false;
174+
let doneHandlerInvoked = false;
176175

177176
worker.on('progress', progress => {
178177
progressUpdates.push(progress);
@@ -181,23 +180,21 @@ describe('Worker', function () {
181180

182181
worker.on('message', () => {
183182
expect(progressUpdates).to.eql([ 0.3, 0.6 ]);
184-
done();
185-
});
186-
});
187-
188-
it('does also emit "done" event', done => {
189-
const progressUpdates = [];
190-
const worker = spawn(progressThread);
191-
192-
worker.on('progress', progress => {
193-
progressUpdates.push(progress);
183+
messageHandlerInvoked = true;
184+
maybeDone();
194185
});
195-
worker.send();
196186

197187
worker.on('done', () => {
198188
expect(progressUpdates).to.eql([ 0.3, 0.6 ]);
199-
done();
189+
doneHandlerInvoked = true;
190+
maybeDone();
200191
});
192+
193+
function maybeDone () {
194+
if (messageHandlerInvoked && doneHandlerInvoked) {
195+
done();
196+
}
197+
}
201198
});
202199

203200

@@ -279,4 +276,22 @@ describe('Worker', function () {
279276

280277
}
281278

279+
// For unknown reasons Firefox will choke on the last test cases
280+
// if the following test cases are not at the end:
281+
// (Only in Firefox, not in Chrome, not in node)
282+
283+
it('can run async method (returning a Promise)', done => {
284+
const worker = spawn((param) => Promise.resolve(param));
285+
canSendAndReceiveEcho(worker, done);
286+
});
287+
288+
it('can handle errors in an async method', done => {
289+
const worker = spawn(() => Promise.reject(new Error('Some error')));
290+
worker.on('error', error => {
291+
expect(error.message).to.match(/^Some error$/);
292+
done();
293+
});
294+
worker.send();
295+
});
296+
282297
});

0 commit comments

Comments
 (0)