-
Notifications
You must be signed in to change notification settings - Fork 4
API document
This is the constructor of Action
which wrap an sync or async action inside, the go
should be a function that wait for a cb
, aka. a continuation, inside this continuation you should call cb
with a value or an Error
sync or async one time, these values will be passed to downstream when Action
are fired, sync or async.
var Action = require('action-js');
var fs = require('fs');
var readFileAction = new Action(
function(cb){
return fs.readFile("data.txt", function(err, data){
if (err) {
cb(err);
}else{
cb(data);
}
});
}
);
_go
is the async action wrapped inside the Action, fire an Action
immediately by passing a cb
to it, it will not delayed to next tick, cb
will receive the value produced by Action
, sync or async one time, no matter it's a Error
or not.
If the continuation has a return value, it will be returned immediately, please check Return value of go
.
readFileAction._go(function(data){
if (data instanceof Error){
console.log('Oh, no...');
}else{
console.log(data);
}
})
Fire an Action
with given cb
, cb
will receive the value produced by Action
only if it's not an Error
, otherwise the Error
will be thrown, leave cb
empty to ignore the value(but an Error
will still be throw), the Error
is thrown asynchronously, so you can't use normal catch
to catch it, put a guard
before go
instead, see below.
If the continuation has a return value, it will be returned immediately, please check Return value of go
.
Return an new Action
with given cb
added to the chain, cb
will receive the value produced by original Action
, no matter it's an Error
or not, the return value of cb
will be pass to downstream, you can return an Action
inside cb
to insert a new async action into the chain.
var processAction = readFileAction
._next(function(data){
if (data instanceof Error){
return 'error handled';
}else{
return processA(data);
}
})
._next(function(data){
return new Action(function(cb){
asyncProcessB(data, cb)
})
})
processAction.go(function(data){
console.log(data);
})
Return an new Action
with given cb
added to the chain, cb
will receive the value produced by original Action
only if it's not an Error
, the return value of cb
will be pass to downstream, you can return an Action
inside the cb
to insert a new async action.
Return an new Action
with given cb
added to the chain, cb
will receive the value produced by original Action
only if it's an Error
, and the return value of cb
will be pass to downstream, you can return an Action
inside the cb
to insert a new async action.
If you pass an prefix
String before cb
, then Error
caught by guard
will be filtered by following condition:
error.message.indexOf(prefix) === 0
So you can use use prefix
to guard certain kind of Error
, other Error
s will flow to downstream.
Return a function that will run fn and return its result if no Error
occured, otherwise return err
.
var FuncMayReturnErr = Action.safe( (new Error 'Error: custom error'), FuncMayThrowErr);
FuncMayReturnErr(params);
// If FuncMayThrowErr(params) throw error, we return Error 'Error: custom error'
You can also use a default value as first parameter for failure instead an Error
instance, this default value will be pass to downstream and processed by following next
. This function is recommended over try..catch
because it can minimize V8 try..catch
overhead.
Return a function that will run fn and return its result if no Error
occured, otherwise return the error.
Wrap a value in an Action
.
Action.wrap('OK');
// it's equivalent to
new Action(function(cb){
return cb('OK');
});
Fire action
immediately(not next tick), memorize the resolved value, and return a new Action that will always give the same value without run the action
again.
// The file will be read now, and saved in dataFrozen
var dataFrozen = Action.freeze(readFileAction.next(process))
dataFrozen.go(function(data){
console.log(data);
})
dataFrozen.go(function(data){
console.log(data);
})
// two console.log should always be the same
Given an array of functions which product Action
, run them in order, use the result of former Action
to produce later Action
, pass the value of the last Action
to downstream, or pass Error
if occurred.
The return value is not a new Action
, but a function which product Action
, pass an argument to it as the init value of the chain.
var accAction = function(x){
return new Action(function(cb){
cb(x+1);
});
}
Action.chain([accAction, accAction, accAction])(0)
.go(function(x){
console.log(x);
// should be 3
})
Return an Action
which will repeat action
n
times, pass the last action
's value to downstream, if stopAtError
is true, any Error
will stop repeating and the error will be passed, repeat forever if n = -1.
Return an Action
delay the action
n
milliseconds.
Action.repeat(-1,
Action.delay(1000, readFileAction)
.next(function(data){
console.log(data);
})
)
.go()
// now the file will be read every 1 second forever.
Return an Action
which will run action
, if failed, retry action
n
times at most, the action will be fired n + 1
times at most, if all failed, an Error
'RETRY_ERROR: Retry limit reached'
will be passed on, retry forever if n == -1.
Return an Action
which will run action in actions
array in parallel and collect results in an Array
, if stopAtError
is true, stop at Error
and pass the error, otherwise save the error in the result array.
Action.parallel([ActionA, ActionB])
.go(function(datas){
console.log(datas[0]); // will be an Error if ActionA return an Error
console.log(datas[1]); // will be an Error if ActionB return an Error
})
Action.parallel([ActionA, ActionB], true)
.next(function(datas){
...
// if ActionA or ActionB return Error, this will be skipped
})
.guard(function(e){
...
})
.go()
This is a lightweight version of parallel
for running two Action
s together, the cb
will receive two results from action1
and action2
, if you don't set stopAtError
to true
, the results may contain Error
s, so guard them if you must.
Action.parallel(ActionA, ActionB, function(data0, data1){
console.log(datas0); // will be an Error if ActionA return an Error
console.log(datas1); // will be an Error if ActionB return an Error
})
.go()
Run action in actions
array in parallel and find the first result, if stopAtError
is true, stop at Error
and pass the error, otherwise continue to look for the first none Error
value.
Run action in actions
array in sequence and collect results in an Array
, if stopAtError
is true, stop at Error
and pass the error, otherwise save the error in the result array.
Run action in actions
array with maximum running number of limit
, if stopAtError
is true, stop at Error
and pass the error, otherwise save the error in the result array. This function can be used to control degree of parallelism, and sequence/parallel
are implemented by it.
Use generator function to chain action in a more imperative way, inside the generator function, you can wait an Action
's value using yield
:
var readFileAction = makeNodeAction(fs.readFile)
var actionFoo = Action.co(function*(paths){
var content = '';
var i = 0;
while(content.length < 1000)
content = yield readFileAction(paths[i++], {encoding: 'utf8'});
if (content !== ''){
console.log(content);
}
});
actionFoo(['./foo.txt', './bar.txt' ... ]).go();
// print the first file content >= 1000
Since Action
's semantic match perfectly with await, the underline implementation is much shorter:
spawn = function(gen, action, cb) {
return action._go(function(v) {
var done, nextAction, ref;
if (v instanceof Error) {
gen["throw"](v);
}
ref = gen.next(v), nextAction = ref.value, done = ref.done;
if (done) {
return cb(v);
} else {
return spawn(gen, nextAction, cb);
}
});
};
Action.co = function(genFn) {
return function() {
var gen = genFn.apply(this, arguments);
return new Action(function(cb) {
return spawn(gen, gen.next().value, cb);
});
};
};
Action.co
is just a fancy way to compose Action
s, but Error
handling will be somehow different, you must use try-catch to wrap everything throw/yield Error
inside generator function, if you want to send value to downstream, wrap it using Action.wrap
and yield
the action, so if you want to use guard
like any other Action
, do it like this:
var actionFoo = Action.co(function*(){
try{
...
// something may throw Error
// or some Action may pass Error
} catch (err) {
// you can deal with the error here
// and return something useful using yield
// or just wrap the Error and pass to downstream
yield Action.wrap(err);
}
});
actionFoo()
.guard(function(err){
// handle err here
})
.next(...)
.go()
Since generator can reuse its executing context, it's faster than splitting them into several functions and connect them with next
, so use co
if you don't need too much modularity, see Benchmark.
This's a very special Action
, when a signal fired, instead of passing value to downstream, it directly return the callback chain, so you can manually fire it with a value, let's call the return value of signal's go
a pump.
testSignal = Action.signal
.guard(function(err){
console.log('fail: ' + err.message);
})
.next(function(data){
console.log('ok: ' + data);
})
testPump = testSignal.go();
// now you can manually fire the `testSignal` anytime you want
testPump('hey');
// ok: hey
testPump(new Error('you'));
// fail: you
This function fuse an array of actions, after each of the signal is pumped, the whole Action will be continue.
var pumps = Action.fuseSignal([ logSignal, logSignal ])
.next(function(data){
console.log(data[0], data[1]);
})
.go()
setTimeout(pumps[0], 100, 'foo');
setTimeout(pumps[1], 1000, 'bar');
// after 100ms, 'foo' will be logged.
// after 1000ms, 'bar' will be logged.
// after 'bar' was logged, true, true will also be logged.
Convert a node style(last argument is a callback with type like (err, data) -> undefined) into a function return Action
.
var readFileAction = Action.makeNodeAction(fs.readFile)
readFileAction('data.txt', {encoding: 'utf8'})
.go(function(data){
console.log(data);
});