Skip to content

Commit 87bcacf

Browse files
committed
Add support for async javascript methods
- #46.
1 parent 208aef5 commit 87bcacf

File tree

3 files changed

+90
-14
lines changed

3 files changed

+90
-14
lines changed

ReadMe.md

+50-8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Jering.Javascript.NodeJS enables you to invoke javascript in [NodeJS](https://no
2727
2828
This library is built to be flexible; you can use a dependency injection (DI) based API or a static API, also, you can invoke both in-memory and on-disk javascript.
2929

30-
Here is an example of invoking javascript using the static API:
30+
Static API example:
3131

3232
```csharp
3333
string javascriptModule = @"
@@ -43,7 +43,7 @@ int result = await StaticNodeJSService.InvokeFromStringAsync<int>(javascriptModu
4343
Assert.Equal(8, result);
4444
```
4545

46-
And here is an example of invoking javascript using the DI based API:
46+
DI based API example:
4747

4848
```csharp
4949
string javascriptModule = @"
@@ -131,8 +131,10 @@ The following section on using `INodeJSService` applies to usage of `StaticNodeJ
131131

132132
### Using INodeJSService
133133
#### Basics
134-
To invoke javascript, we'll first need to create a NodeJS module that exports a function or an object containing functions. These functions must take
135-
a callback as their first argument, and they must call the callback.
134+
To invoke javascript, we'll first need to create a [NodeJS module](#nodejs-modules) that exports a function or an object containing functions. Exported functions can be of two forms:
135+
136+
##### Function With Callback Parameter
137+
These functions must take a callback as their first argument, and they must call the callback.
136138
The callback takes two optional arguments:
137139
- The first argument must be an error or an error message. It must be an instance of type [`Error`](https://nodejs.org/api/errors.html#errors_class_error) or a `string`.
138140
- The second argument is the result. It must be an instance of a JSON-serializable type, a `string`, or a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
@@ -143,17 +145,18 @@ if you'd like to learn more about how asynchrony works in NodeJS).
143145

144146
This is a module that exports a valid function:
145147
```javascript
146-
module.exports = (callback) => {
147-
... // Do something
148+
module.exports = (callback, arg1, arg2, arg3) => {
149+
... // Do something with args
148150

149151
callback(null, result);
150152
}
151153
```
154+
152155
And this is a module that exports an object containing valid functions:
153156
```javascript
154157
module.exports = {
155-
doSomething: (callback) => {
156-
... // Do something
158+
doSomething: (callback, arg1) => {
159+
... // Do something arg
157160

158161
callback(null, result);
159162
},
@@ -165,6 +168,45 @@ module.exports = {
165168
}
166169
```
167170

171+
##### Async Function
172+
Async functions are really just syntactic sugar for functions with callback parameters.
173+
[Callbacks, Promises and Async/Await](https://medium.com/front-end-weekly/callbacks-promises-and-async-await-ad4756e01d90) provides a nice summary on how callbacks, promises and async/await work.
174+
175+
This is a module that exports a valid function:
176+
```javascript
177+
module.exports = async (arg1, arg2) => {
178+
... // Do something with args
179+
180+
return result;
181+
}
182+
```
183+
184+
And this is a module that exports an object containing valid functions:
185+
```javascript
186+
module.exports = {
187+
doSomething: async (arg1, arg2, arg3, arg4) => {
188+
... // Do something with args
189+
190+
// async functions can explicitly return promises
191+
return new Promise((resolve, reject) => {
192+
resolve(result);
193+
});
194+
},
195+
doSomethingElse: async (arg1) => {
196+
... // Do something with arg
197+
198+
return result;
199+
}
200+
}
201+
```
202+
203+
If an error is thrown it is caught and handled by the caller (error message is sent back to the calling .Net process):
204+
```javascript
205+
module.exports = async () => {
206+
throw new Error('error message');
207+
}
208+
```
209+
168210
#### Invoking Javascript From a File
169211
If we have a file named `exampleModule.js` (located in [`NodeJSProcessOptions.ProjectPath`](#nodejsprocessoptions)), with contents:
170212
```javascript

src/NodeJS/Javascript/Servers/OutOfProcess/Http/HttpServer.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const server = http.createServer((req, res) => {
2828
let bodyChunks = [];
2929
req.
3030
on('data', chunk => bodyChunks.push(chunk)).
31-
on('end', () => {
31+
on('end', async () => {
3232
try {
3333
// Create InvocationRequest
3434
let body: string = Buffer.concat(bodyChunks).toString();
@@ -41,7 +41,6 @@ const server = http.createServer((req, res) => {
4141
invocationRequest = JSON.parse(body);
4242
}
4343

44-
4544
// Get exports of module specified by InvocationRequest.moduleSource
4645
let exports: any;
4746
if (invocationRequest.moduleSourceType === ModuleSourceType.Cache) {
@@ -143,10 +142,14 @@ const server = http.createServer((req, res) => {
143142
}
144143

145144
// Invoke function
146-
let args: object[] = [callback];
147-
functionToInvoke.apply(null, args.concat(invocationRequest.args));
148-
} catch (synchronousError) {
149-
respondWithError(res, synchronousError);
145+
if (functionToInvoke.constructor.name === "AsyncFunction") {
146+
callback(null, await functionToInvoke.apply(null, invocationRequest.args));
147+
} else {
148+
let args: object[] = [callback];
149+
functionToInvoke.apply(null, args.concat(invocationRequest.args));
150+
}
151+
} catch (error) {
152+
respondWithError(res, error);
150153
}
151154
});
152155
}).listen(parseInt(args.port), 'localhost', function () {

test/NodeJS/HttpNodeJSServiceIntegrationTests.cs

+31
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,37 @@ public async void AllInvokeMethods_ThrowInvocationExceptionIfInvokedMethodCallsC
341341
Assert.StartsWith(dummyErrorString, result.Message); // Complete message includes the stack
342342
}
343343

344+
[Fact(Timeout = _timeoutMS)]
345+
public async void AllInvokeMethods_ThrowInvocationExceptionIfInvokedAsyncMethodThrowsError()
346+
{
347+
// Arrange
348+
const string dummyErrorString = "error";
349+
HttpNodeJSService testSubject = CreateHttpNodeJSService();
350+
351+
// Act
352+
InvocationException result = await Assert.ThrowsAsync<InvocationException>(() =>
353+
testSubject.InvokeFromStringAsync<DummyResult>("module.exports = async (errorString) => {throw new Error(errorString);}", args: new[] { dummyErrorString })).
354+
ConfigureAwait(false);
355+
356+
// Assert
357+
Assert.StartsWith(dummyErrorString, result.Message); // Complete message includes the stack
358+
}
359+
360+
[Fact(Timeout = _timeoutMS)]
361+
public async void AllInvokeMethods_InvokeAsyncJavascriptMethods()
362+
{
363+
// Arrange
364+
const string dummyResultString = "success";
365+
HttpNodeJSService testSubject = CreateHttpNodeJSService();
366+
367+
// Act
368+
DummyResult result = await testSubject.
369+
InvokeFromStringAsync<DummyResult>("module.exports = async (resultString) => {return {result: resultString};}", args: new[] { dummyResultString }).ConfigureAwait(false);
370+
371+
// Assert
372+
Assert.Equal(dummyResultString, result.Result);
373+
}
374+
344375
[Fact(Timeout = _timeoutMS)]
345376
public async void AllInvokeMethods_InvokeASpecificExportIfExportNameIsProvided()
346377
{

0 commit comments

Comments
 (0)