vow-asy v0.1.1
vow-asy
Promise-oriented tamer of ASYnchronous operations based on vow
Installation
Node.js
You can install it using npm:
npm install vow vow-asy
Browser
<script type="text/javascript" src="vow.min.js"></script>
<script type="text/javascript" src="vow-asy.min.js"></script>
The following formats of module's defining are also supported: RequireJS, YM.
Motivation
Why?!
Is it always convenient to use the way of standard Promises chaining?
someAsync().then().then().then()
In general - yes. But sometimes it is not convenient and at times not enough at all!
Then follows boring (but extremely useful) docs. If you do want to take a look at asy in action then you may direct your eyes on this, slightly synthetic example and turn back here again.
Usage
Basic states:
Target
In ASY the target is a data, which will be transfered to the iterative operation. The Target may be Promise (thenable), Object, Array. An Element of Target may be any type of data, including Promise instances (to say more precisely - any thenable) and falsy-values. In case Target is Object - its element has the value of the attributes of the object. If Target - Array, then Element of target is the array's element. If Target is - Promise, then Element will be the result of this promise execution.
Important: some methods may have a specific behavior when processing Elements of Function type. Each method has a note on the matter.
Operation execution context (AsyCtx)
Some methods provide a simple interface for context information obtaining, through which you can control the entire operation in most methods.
Context props:
index
Number
Iteration index
key
String|Number
For an array as Target - index, for an object - property name
prev
*
The execution result of the previous iteration.
Some methods do not provide such functionalityresult
[]|Object
The accumulated result of operation.
Available only for#map*
methods
Operation handling (methods are not available in
#map*
operations):defer
function((function(success, fail, fulfill): void)?): AsyCtx
Make current iteration asyncronous
success
function(data: *?): void
Successfully complete current iteration
fulfill
function(data: *?): void
Complete operation entirely
fail
function(error: *?): void
Complete operation with error
delegateFulfill
function(data: Promise|*): void
Complete operation after promise execution with result transfer
notify
function(data: *?): void
Notify subscribers of operation progress
Operation execution options
- sort
Boolean|function(item1: String|*, item2: String|*): Number
Sorting. Target type Array acts similar to the methodArray#sort
For Target type Object - only its keys are exposed to sorting. separate
Boolean|Number
Separation. If true - iterators will be invoked through a method callAfter If value a number then, if 0 - iterators will be invoked through a method callLater, else - through setTimeout
stackLimit
Boolean|Number
This option allow to avoid stack overflow that can occur when an abundance of operations does not contribute to its release. Standard limitation - 1000, it will be assigned if
true
.
Important: value cannot be less then 100.notify
"after"|"before"|"both"|"none"|Boolean
Promise progress notifying returned by methods.
Type Description after
notify after iteration before
notify before iteration both
combination of before
andafter
none
do not notify true
same as after
false
same as none
- initialValue
*
Initial value for reducing
- sort
Operation execution progress fields
index
Number
Iteration index.
key
String|Number
For Array as Target - Index, for Object - property name.
kind
"before"|"after"
Kind.
before
Before iterator's workafter
After iterator's work
data
*
Iteration data - Element of Target.
result
[]|Object
Accumulated operation result.
Available only for#map*
methods
Functionality:
Deferred calls
Execution asynchronization helpers
callLater (immediately)
Cross-platform implementation setImmediate
asy#callLater: void;
asy#immediately: void;
Argument | Type |
---|---|
func | Function |
thisObject | Object |
...restArgs | * |
Example:
asy.callLater(
function() {
console.log(
"I'm called immediately on context ", JSON.stringify(this),
"I was given this [",
Array.prototype.slice.call(arguments).join(", "),
"]!"
);
//-> I'm called immediately on context {"propOfContext":true} I was given this [ 1, 2, 3 ]!
},
{ propOfContext: true },
1, 2, 3
)
callAfter (nextTick)
Cross-platform implementation nextTick
asy#callAfter: void;
asy#nextTick: void;
Argument | Type |
---|---|
func | Function |
thisObject | Object |
...restArgs | * |
applyLater
Variation asy#callLater
with argument's array instead of rest-arguments
asy#applyLater: void;
Argument | Type |
---|---|
func | Function |
thisObject | Object |
args | *[] |
Example:
asy.applyLater(
function() {
console.log(
"I'm called immediately on context ", JSON.stringify(this),
"I was given this [",
Array.prototype.slice.call(arguments).join(", "),
"]!"
);
//-> I'm called immediately on context {"propOfContext":true} I was given this [ 1, 2, 3 ]!
},
{ propOfContext: true },
[1, 2, 3]
)
applyAfter
Variation asy#callAfter
with argument's array instead of rest-arguments
asy#applyAfter: void;
Argument | Type |
---|---|
func | Function |
thisObject | Object |
args | *[] |
Chaining
Cool way for sequence execution expression
seq
Method consistently iterate and process all arguments passed to it. Arguments can be any type. But if it is Promise then iteration continue only after it will be done. This is not all. If the next argument is a function, it will be executed and when you call it will be given the result of the previous step as the first argument and execution context (AsyCtx) as second (this is very simple thing, that allow you to control the entire chain).
Method returns Promise, which execution progress you can track with a help of third argument of method then
.
Should not forget about implicit limitation of certain environments on the number of arguments for called function.
asy#seq: Promise
Argument | Type |
---|---|
...restArgs | *|Promise|function(prev: *, actx: AsyCtx) |
Example:
var p;
asy.seq(
1, 2, 3, 4,
vow.resolve(5),
6, 7,
function (prev, actx) {
return actx.defer().success(8);
},
function (prev, actx) {
p = prev + actx.index + actx.key;
actx.defer().fulfill(111);
},
12
).then(
function (data) {
console.log(p); //-> 24
console.log(data); //-> 111
},
function (error) {
console.error("Oops:", error);
},
function (progress) {
console.log("STEP:", progress);
//->
// STEP: {index: 0, key: 0, data: 1, kind: "after"}
// STEP: {index: 1, key: 1, data: 2, kind: "after"}
// STEP: {index: 2, key: 2, data: 3, kind: "after"}
// STEP: {index: 3, key: 3, data: 4, kind: "after"}
// STEP: {index: 4, key: 4, data: 5, kind: "after"}
// STEP: {index: 5, key: 5, data: 6, kind: "after"}
// STEP: {index: 6, key: 6, data: 7, kind: "after"}
// STEP: {index: 7, key: 7, data: 8, kind: "after"}
// STEP: {index: 8, key: 8, data: 111, kind: "after"}
}
);
chain
More manageable alternative asy#seq
is asy#chain
.
This method introduces the concepts of Target and its Elements.
Target can be Objects, Arrays, Promises. Elements of Target can be any type data including Promises.
As elements of Target method can take a functions that will be called and processed as well as the method asy#seq
.
Available options: separate, stackLimit, sort, notify
asy#chain: Promise
Argument | Type |
---|---|
target | Promise|Object|Function|[] |
thisObject | Object |
options | Object |
Example:
// Target is array
var p;
asy.chain([
1, 2, 3, 4,
vow.resolve(5),
6, 7,
function (prev, actx) {
return actx.defer().success(8);
},
function (prev, actx) {
p = prev + actx.index + actx.key;
actx.defer().fulfill(111);
},
12
]).then(function (data) {
console.log(p); //-> 24
console.log(data); //-> 111
});
// Target is object
asy.chain({
"p1": vow.resolve(3),
"p2": function (prev) { return prev + 16; },
"p3": function (prev, actx) {
actx.defer(function (fulfill, success, fail) {
success(prev + 10);
});
},
"p4": function (prev) {
return new vow.Promise(function (resolve) {
setTimeout(function () {
resolve(prev + 11);
}, 0);
});
}
}).then(function (data) {
console.log(data); //-> 40
});
Iterating
Cool way for consistent iteration of elements
eachSeries
Method allow to iterate all elements of Target consistently, calling iterator for each.
Available options: separate, stackLimit, sort, notify
asy#eachSeries: Promise
Argument | Type |
---|---|
target | Promise|Object|Function|[] |
thisObject | Object |
iterator | function(item: *, actx: AsyCtx): *|Promise |
options | Object |
eachPooled
Method allow to iterate all elements of Target consistently, organizing asynchronous iterator call to the pool the size of which is specified in poolSize argument.
Available options: separate, sort, notify
asy#eachPooled: Promise
Argument | Type |
---|---|
poolSize | Number |
target | *[]|Object|Promise |
thisObject | Object |
iterator | function(item: *, actx: AsyCtx): *|Promise |
options | Object |
Mapping
This group of methods allow to iterate Target the same way as Array#map
map
Available opttions: sort, notify
asy#map: Promise
Argument | Type |
---|---|
target | *[]|Object|Promise |
iterator | function(item: *, actx: { key: String|Number, index: Number, result: Object|*[] }): *|Promise |
thisObject | Object |
Example:
asy.map(
{
"p1": vow.resolve(3),
"p2": 2,
"p3": function () {
return 3;
},
"p4": function () {
return new vow.Promise(function (resolve) {
setTimeout(function () {
resolve(11);
}, 0);
});
}
},
function (item) {
if (typeof item === "function")
return item();
return item;
}
).then(function (data) {
console.log(data);//-> { p2: 2, p3: 3, p1: 3, p4: 11 }
});
mapSeries
Available options: separate, stackLimit, sort, notify
asy#mapSeries: Promise
Argument | Type |
---|---|
target | *[]|Object|Promise |
iterator | function(item: *, actx: { key: String|Number, index: Number, result: Object|*[]}): *|Promise |
thisObject | Object |
options | Object |
Example:
asy.mapSeries([
vow.resolve(3),
function () {
return new vow.Promise(function (resolve) {
setTimeout(function () {
resolve(11);
}, 0);
});
},
10
], function (item, actx) {
if (typeof item === "function")
return item();
return item;
}).then(function (data) {
console.log(data);//-> [3, 11, 10]
});
mapPooled
Available options: separate, sort, notify
asy#mapPooled: Promise
Argument | Type |
---|---|
poolSize | Number |
target | *[]|Object|Promise |
iterator | function(item: *, actx: { key: String|Number, index: Number, result: Object|*[]}): *|Promise |
thisObject | Object |
options | Object |
Example:
asy.mapPooled(
2,
{
"p1": 1,
"p2": function () {
return new vow.Promise(function (resolve) {
setTimeout(function () {
resolve(2);
}, 1);
});
},
"p3": vow.resolve(3),
"p4": function () {
return new vow.Promise(function (resolve) {
setTimeout(function () {
resolve(4);
}, 0);
});
},
"p5": 5
},
function (item) {
if (typeof item === "function")
return item();
return item;
}
).then(function (data) {
console.log(data); //-> { p1: 1, p3: 3, p2: 2, p5: 5, p4: 4 }
});
Reducing
This group of methods allow to iterate Target the same way as method Array#reduce
reduce
Available options: sort, initialValue
asy#reduce: Promise
Argument | Type |
---|---|
target | *[]|Object|Promise |
reducer | function(prev: *, curr: *, actx: { key: String|Number, index: Number, target: *[]|Object }) |
thisObject | Object |
options | Object |
Example:
asy.reduce(
{
p1: vow.resolve("a"),
p2: "b",
p3: "c"
},
function (prev, curr, actx) {
return prev
+ (actx.index === 0 ? "" : this["val"])
+ curr
+ "(" + actx.key + ":" + actx.index + ")";
},
{ val: "+" },
{ initialValue: "R=" }
).then(function (result) {
console.log(result);//->"R=a(p1:0)+b(p2:1)+c(p3:2)"
});
NotComplicated application example
Suppose we have a complicated task.
Generally:
- We need to merge in one big report the results of an entire group of smaller reports.
- Router-file contains information about all those reports that we need to merge and also their identification code.
- The result of merge should be published in agreed manner.
In details:
- Report details files are very peculiar as they can be formed by a remote system providing a link to the report.
- Our system is critical to high loads - we can't download more than 2 report-files simultaneously.
- Each report file contains sequence number in its identification code before
@
symbol. Summary report linker uses it for controlling the order of output. - Also: lets assume it architect's whim that reports should be merged and downloaded the same order!
- Format of report files is not as reliable as we would like, and this should be considered.
- Basic moments of entire operation should be logged!
- If an error of loading, merging or report content processing occurs than the whole operation should be ended immediatly
var routerFileUrl = "https://www.com/report-router.json";
/**
* Asynchronous download file, parse it as JSON and return content
* @param {!String} url
* @return Promise
*/
function downloadAndParseFile(url) {/*...*/}
/**
* Asynchronous publish report data
* @param {!String} dataOfReport
* @return Promise
*/
function publishReport(dataOfReport) {/*...*/}
// Организуем цепочку вызовов
asy.chain({
// Pass router-file url
"Provide router url": routerFileUrl,
// Download and parse
"Download router": downloadAndParseFile,
// Merge reports files
"Build report": function (/**{ reports: Object }*/content, actx) {
//content: {
// reports: {
// "3@identity-of-file-report6": "http://ww.ru/files/file6.json",
// "1@identity-of-file-report1": "http://ww.ru/files/file1.json",
// "2.1@identity-of-file-report5": "http://ww.ru/files/file5.json",
// "1.1@identity-of-file-report2": "http://ww.ru/files/file2.json",
// "2@identity-of-file-report4": "http://ww.ru/files/file4.json"
// "1.2@identity-of-file-report3": "http://ww.ru/files/file3.json"
// }
//}
var
/**
* Extract the report index
* @param {!String} identity
* @return {Number}
*/
extractReportIndex = function (identity) {
return parseFloat(identity.substr(0, identity.indexOf("@")))
},
/**
* Sort comparator
* @param {!String} identity1
* @param {!String} identity2
* @return {Number}
*/
reportIdentityComparator = function (identity1, identity2) {
return extractReportIndex(identity2) - extractReportIndex(identity1);
};
// No reports - end of the whole operation
if (!content.reports) {
actx.notify("No reports for build");
actx.fulfill();
} else {
// In sequence
return asy.seq(
// Process reports files consistently
asy.mapPooled(
2, // No more than two iterations simultaneously
content.reports, // Files list as iteration Target
downloadAndParseFile, // Files downloader - as iterator
null, // No need in context of
// Iteration options
{
// Reports should be sorted by index that is specified in their identifier
sort: reportIdentityComparator
}
),
// Check reports data, process external systems reports
function (/**Object.<String, { data: *, delegatedDataUrl: String }>*/reportMap) {
//reportMap = {
// "3@identity-of-file-report6": {
// data: "Long long report data..."
// },
// "1@identity-of-file-report1": {
// delegatedDataUrl: "http://other-system/repo?d=1&s='t'"
// },
// ...
//}
/**
* Simple report checker
* @param {{ data: *, delegatedDataUrl: String }} report
* @throw
*/
var checkReportData = function (/**{}*/report) {
var idx = 0;
if ("data" in report) ++idx;
if ("delegatedDataUrl" in report) ++idx;
if (idx > 1) throw new Error("Ambiguous condition report");
else if (idx === 0) throw new Error("Empty report");
};
return asy.mapPooled(
2,
reportMap,
function (/**{ data: *, delegatedDataUrl: String }*/report) {
checkReportData(report);
return report.data
? report.data
: downloadAndParseFile(report.delegatedDataUrl)
.then(function (/**{ data: String }*/externalReport) {
checkReportData(externalReport);
return externalReport.data;
});
}
);
},
// Link received data
function (/**Object.<String, String>*/reportMap) {
return asy.reduce(
reportMap,
function (/**String*/data1, /**String*/data2) {
return data1 + "\r\n" + data2;
},
null,
{ sort: reportIdentityComparator }
);
}
);
}
},
// Publish merged result
"Publish": publishReport
}, null, { notify: "both" }).then(
// Callback
function () {
console.log("Reporting successful");
},
// Errback
function (error) {
console.error("Reporting failed", error);
},
// Progback
function (/**{ key: String, kind: ("before"|"after") }*/aprog) {
console.log("[" + (aprog.kind === "after" ? "DONE" : "BEGIN") + "]", aprog.key);
}
);
Interested? Then back for more details
If this document examples seem insufficient than you're welcome to tests provided with packet. Most of them similar to syntax example of usage, not a unit-test.
TODO
- Differentiated tests
@public
access toAsyQ
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago