0.1.1 • Published 8 years ago

vow-asy v0.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
8 years ago

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:

  1. 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.

  2. 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 functionality

    • result []|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

  3. Operation execution options

    • sort Boolean|function(item1: String|*, item2: String|*): Number Sorting. Target type Array acts similar to the method Array#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.

      TypeDescription
      afternotify after iteration
      beforenotify before iteration
      bothcombination of before and after
      nonedo not notify
      truesame as after
      falsesame as none
    • initialValue * Initial value for reducing
  4. 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 work
      • after After iterator's work
    • data *

      Iteration data - Element of Target.

    • result []|Object

      Accumulated operation result.
      Available only for #map* methods

Functionality:

  1. Deferred calls
  2. Chaining
  3. Iterating
  4. Mapping
  5. Reducing

Deferred calls

Execution asynchronization helpers

callLater (immediately)

Cross-platform implementation setImmediate

asy#callLater: void; asy#immediately: void;

ArgumentType
funcFunction
thisObjectObject
...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;

ArgumentType
funcFunction
thisObjectObject
...restArgs*

applyLater

Variation asy#callLater with argument's array instead of rest-arguments

asy#applyLater: void;

ArgumentType
funcFunction
thisObjectObject
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;

ArgumentType
funcFunction
thisObjectObject
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

ArgumentType
...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

ArgumentType
targetPromise|Object|Function|[]
thisObjectObject
optionsObject

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

ArgumentType
targetPromise|Object|Function|[]
thisObjectObject
iteratorfunction(item: *, actx: AsyCtx): *|Promise
optionsObject

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

ArgumentType
poolSizeNumber
target*[]|Object|Promise
thisObjectObject
iteratorfunction(item: *, actx: AsyCtx): *|Promise
optionsObject

Mapping

This group of methods allow to iterate Target the same way as Array#map

map

Available opttions: sort, notify

asy#map: Promise

ArgumentType
target*[]|Object|Promise
iteratorfunction(item: *, actx: { key: String|Number, index: Number, result: Object|*[] }): *|Promise
thisObjectObject

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

ArgumentType
target*[]|Object|Promise
iteratorfunction(item: *, actx: { key: String|Number, index: Number, result: Object|*[]}): *|Promise
thisObjectObject
optionsObject

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

ArgumentType
poolSizeNumber
target*[]|Object|Promise
iteratorfunction(item: *, actx: { key: String|Number, index: Number, result: Object|*[]}): *|Promise
thisObjectObject
optionsObject

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

ArgumentType
target*[]|Object|Promise
reducerfunction(prev: *, curr: *, actx: { key: String|Number, index: Number, target: *[]|Object })
thisObjectObject
optionsObject

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 to AsyQ
0.1.1

8 years ago

0.1.0

9 years ago

0.1.0-beta5

9 years ago

0.1.0-beta4

9 years ago

0.1.0-beta3

9 years ago

0.1.0-beta2

9 years ago

0.1.0-beta1

9 years ago

0.1.0-alpha2

9 years ago

0.1.0-alpha1

9 years ago