- Introduction
- API
create(id: string, settings: ZoneSettings = DEFAULT_SETTINGS): Zone
get(id: string): Zone
current: Zone
node: Zone
- Interface
ZoneSettings
- Object
DEFAULT_SETTINGS: ZoneSettings
- Interface
Zone
zone.id: string
zone.broadcast(code: string): Promise<void>
zone.broadcast(function: (...args: any[]) => void, args?: any[]): Promise<void>
zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise<Result>
zone.execute(function: (...args[]) => any, args?: any[], options?: CallOptions): Promise<Result>
- Interface
CallOptions
- Interface
Result
Zone is a key concept of napajs that exposes multi-thread capabilities in JavaScript world, which is a logical group of symmetric workers for specific tasks.
Please note that it's not the same zone
concept of a context object for async calls in Dart, or Angular, or a proposal in TC39.
Zone consists of one or multiple JavaScript threads, we name each thread worker
. Workers within a zone are symmetric, which means code executed on any worker from the zone should return the same result, and the internal state of every worker should be the same from a long-running point of view.
Multiple zones can co-exist in the same process, with each loading different code, bearing different states or applying different policies, like heap size, etc. The purpose of having multiple zone is to allow multiple roles for complex work, each role loads the minimum resources for its own usage.
There are two types of zone:
- Napa zone - zone consists of Napa.js managed JavaScript workers (V8 isolates). Can be multiple, each may contain multiple workers. Workers in Napa zone support partial Node.JS APIs.
- Node zone - a 'virtual' zone which exposes Node.js eventloop, has access to full Node.js capabilities.
There are two operations, designed to reinforce the symmetry of workers within a zone:
- Broadcast - run code that changes worker state on all workers, returning a promise for the pending operation. Through the promise, we can only know if the operation succeed or failed. Usually we use
broadcast
to bootstrap the application, pre-cache objects, or change application settings. FunctionbroadcastSync
is also offered as a synchronized version of broadcast operations. - Execute - run code that doesn't change worker state on an arbitrary worker, returning a promise of getting the result. Execute is designed for doing the real work.
Zone operations are on a basis of first-come-first-serve, while broadcast
takes higher priority over execute
.
It creates a Napa zone with a string id. If zone with the id is already created, error will be thrown. ZoneSettings
can be specified for creating zones.
Example 1: Create a zone with id 'zone1', using default ZoneSettings.
var napa = require('napajs');
var zone1 = napa.zone.create('zone1');
Example 2: Create a zone with id 'zone2', with 1 worker.
var zone2 = napa.zone.create('zone2', {
workers: 1
});
It gets a reference of zone by an id. Error will be thrown if the zone doesn't exist.
Example:
var zone = napa.zone.get('zone1');
It returns a reference of the zone of the currently running isolate. If it's under node, it returns the node zone.
Example: Get current zone.
var zone = napa.zone.current;
It returns a reference to the node zone. It is equivalent to napa.zone.get('node')
;
Example:
var zone = napa.zone.node;
Settings for zones, which will be specified during the creation of zones. If not specified, DEFAULT_SETTINGS will be used.
Number of workers in the zone.
Default settings for creating zones.
{
workers: 2
}
Zone is the basic concept to execute JavaScript and apply policies in Napa. You can find its definition in Introduction. Through the Zone API, developers can broadcast JavaScript code on all workers, or execute a function on one of them. When you program against a zone, it is the best practice to ensure all workers within a zone are symmetrical to each other, that is, you should not assume a worker may maintain its own states.
The two major sets of APIs are broadcast
and execute
, which are asynchronous operations with a few variations on their inputs.
It gets the id of the zone.
It asynchronously broadcasts a snippet of JavaScript code in a string to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.
Example:
var napa = require('napajs');
var zone = napa.zone.get('zone1');
zone.broadcast('var state = 0;')
.then(() => {
console.log('broadcast succeeded.');
})
.catch((error) => {
console.log('broadcast failed.')
});
It synchronously broadcasts a snippet of JavaScript code in a string to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.
Remarks:
- It's not allowed to call
broadcastSync
on current zone. It will cause a deadlock
Example:
var napa = require('napajs');
var zone = napa.zone.get('zone1');
try {
zone.broadcastSync('var state = 0;');
console.log('broadcast succeeded.');
} catch (error) {
console.log('broadcast failed.')
}
It asynchronously broadcasts an anonymous function with its arguments to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.
Remarks:
- If the function returns a Promise object, its state will be adopted to
broadcast
's return value - The function object cannot access variables from closure
- Unless the function object has an
origin
property, it will use the current file asorigin
, which will be used to set__filename
and__dirname
. (See transporting functions) - Transport context is not available in broadcast. All types that depend on TransportContext (eg. ShareableWrap, Transportable) cannot be passed in arguments list.
Example:
zone.broadcast((state) => {
require('some-module').setModuleState(state)
}, [{field1: 1}])
.then(() => {
console.log('broadcast succeeded.');
})
.catch((error) => {
console.log('broadcast failed:', error)
});
It synchronously broadcasts an anonymous function with its arguments to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.
Remarks:
- It's not allowed to call
broadcastSync
on current zone. It will cause a deadlock - If the function returns a Promise object, its state will be adopted. Function
broadcastSync
will not return until that Promise resolved or rejected. - The function object cannot access variables from closure
- Unless the function object has an
origin
property, it will use the current file asorigin
, which will be used to set__filename
and__dirname
. (See transporting functions) - Transport context is not available in broadcast. All types that depend on TransportContext (eg. ShareableWrap, Transportable) cannot be passed in arguments list.
Example:
try {
zone.broadcastSync((state) => {
require('some-module').setModuleState(state)
}, [{field1: 1}]);
console.log('broadcast succeeded.');
} catch (error) {
console.log('broadcast failed:', error)
}
zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise<any>
Execute a function asynchronously on an arbitrary worker via module name and function name. Arguments can be of any JavaScript type that is transportable. It returns a Promise of Result
. If an error happens, either bad code, user exception, or timeout is reached, the promise will be rejected.
Example: Execute function bar
in module foo
, with arguments [1, 'hello', { field1: 1 }]. 300ms timeout is applied.
zone.execute(
'foo',
'bar',
[1, "hello", {field1: 1}],
{ timeout: 300 })
.then((result) => {
console.log('execute succeeded:', result.value);
})
.catch((error) => {
console.log('execute failed:', error);
});
Execute a function object asynchronously on an arbitrary worker. Arguments can be of any JavaScript type that is transportable. It returns a Promise of Result
. If an error happens, either bad code, user exception, or timeout is reached, promise will be rejected.
Remarks:
- If the function returns a Promise object, it will be adopted
- The function object cannot access variables from closure
- Unless the function object has an
origin
property, it will use the current file asorigin
, which will be used to set__filename
and__dirname
. (See transporting functions)
Example:
zone.execute((a: number, b: string, c: object) => {
return a + b + JSON.stringify(c);
}, [1, "hello", {field1: 1}])
.then((result) => {
console.log('execute succeeded:', result.value);
})
.catch((error) => {
console.log('execute failed:', error);
});
Output:
execute succeeded: 1hello{"field1":1}
Another example demonstrates accessing __filename
when executing an anonymous function:
// File: /usr/file1.js
zone.execute(() => { console.log(__filename);});
Output:
/usr/file1.js
Interface for options to call functions in zone.execute
.
Timeout in milliseconds. Default value 0 indicates no timeout.
Interface to access the return value of execute
.
JavaScript value returned from the function which is invoked from zone.execute/executeSync. Napa marshalls/unmarshalls transportable values between different workers (V8 isolates). Unmarshalling will happen when the first result.value
is queried.
Example:
var value = result.value;
Marshalled payload (in JSON) from the returned value. This field is for users that want to pass results through to its caller, where the unmarshalled value is not required.
Example:
var payload = result.payload;
TransportContext that is required to unmarshall result.payload
into result.value
.
Example:
var napa = require('napajs');
var zone = napa.zone.create('zone1');
zone.execute(() => { return 0; }, [])
.then((result) => {
// Manually marshall.
var transportContext = result.transportContext;
var value = napa.transport.unmarshall(result.payload, result.transportContext);
// result.value and manual unmarshall from payload are the same.
assert.equal(value, result.value);
});