1. 程式人生 > >async源碼學習 - waterfall函數的使用及原理實現

async源碼學習 - waterfall函數的使用及原理實現

color logs 這一 per () create was ret ray

waterfall函數會連續執行數組中的函數,每次通過數組下一個函數的結果。
然而,數組任務中的任意一個函數結果傳遞失敗,那麽該函數的下一個函數將不會執行,
並且主回調函數立馬把錯誤作為參數執行。

以上是我翻譯的,我都不知道翻譯的什麽鬼。

其實該函數的作用就是: 上一個異步函數返回結果可以傳給下一個異步函數,如果傳遞過程中,第一個參數出錯了也就是真值的話,
下一個回調函數將會停止調用,並且直接調用waterfall函數的第二個參數,其實也是個回調函數。並且把錯誤的參數傳過去。

先看看官網的demo:
        async.waterfall([
            fn1,
            fn2,
            fn3
        ], 
function(err, arg1) { console.log(err); console.log(arg1); }); function fn1(next) { next(null, ‘11‘); }; function fn2(arg1, next) { console.log(arg1); next(null, ‘22‘, ‘33‘); }; function fn3(arg1, arg2, next) { console.log(arg1); console.log(arg2); next(
null, ‘done‘); };

        async.waterfall([function(aaa) {
            console.log(11);
            aaa(null, ‘one‘);
        }, function(arg1, bbb) {
            console.log(arg1);
            bbb(null, ‘two‘, ‘three‘);
        }, function(arg1, arg2, ccc) {
            console.log(arg1);
            console.log(arg2);
            ccc(
null, ‘down‘, ‘down2‘); }], function(err, result, res2) { console.log(err); console.log(result); console.log(res2); });

自己搞個創建文件的實例,看看

class File {
            constructor() {}

            // 創建文件
            createFile(callback) {
                setTimeout(() => {
                    if (0) {
                        console.log(‘創建文件失敗‘);
                        callback(‘err‘);
                    } else {
                        console.log(‘創建文件成功‘);
                        callback(null);
                    };
                }, 3000);
            }

            // 寫文件
            writeFile(callback) {
                setTimeout(() => {
                    if (1) {
                        console.log(‘寫文件失敗‘);
                        callback(‘err‘);
                    } else {
                        console.log(‘寫文件成功‘);
                        callback(null);
                    };
                }, 2000);
            }

            // 讀文件
            readFile(callback) {
                setTimeout(() => {
                    if (0) {
                        console.log(‘讀文件失敗‘);
                        callback(‘err‘);
                    } else {
                        console.log(‘讀文件成功‘);
                        callback(null, ‘I love async!‘);
                    };
                }, 4000);
            }
        };
        let file = new File();

        async.waterfall([function(callback) {
            file.createFile(function(err) {
                if (!err) {
                    callback(null, ‘createFile Ok‘);
                } else {
                    callback(‘createFileFail‘);
                };
            });
        }, function(err, callback) {
            file.writeFile(function(err) {
                if (!err) {
                    callback(null, ‘writeFile Ok‘);
                } else {
                    callback(‘writeFileFail‘);
                };
            });
        }, function(err, callback) {
            file.readFile(function(err) {
                if (!err) {
                    callback(null, ‘readFile Ok‘);
                } else {
                    callback(‘readFileFail‘);
                };
            });
        }], function(err, result) {
            console.log(err);
            console.log(result);
        });

我一直納悶,他怎麽做到,上一個異步什麽時候做完後,通知下一個異步開始執行,並且把參數傳給下一個異步函數的。看看源碼實現:

/**
 * Created by Sorrow.X on 2017/5/28.
 */

var waterfall = (function() {

    var isArray = Array.isArray;    // 把數組的isArray賦給isArray變量

    // 是否支持Symbol
    var supportsSymbol = typeof Symbol === ‘function‘;

    var setImmediate$1 = wrap(_defer);

    function wrap(defer) {
        return function (fn/*, ...args*/) {
            var args = slice(arguments, 1);
            defer(function () {
                fn.apply(null, args);
            });
        };
    };

    // 是否是異步
    function isAsync(fn) {
        return supportsSymbol && fn[Symbol.toStringTag] === ‘AsyncFunction‘;
    };

    // 空函數
    function noop() {
        // No operation performed.
    };

    // 一次(偏函數)
    function once(fn) {    // fn: waterfall的第二個參數(回調函數)
        return function () {
            if (fn === null) return;
            var callFn = fn;
            fn = null;    // 把上級函數作用域中的fn置空
            callFn.apply(this, arguments);    // 調用回調函數
        };
    };

    // 包裝成異步
    function wrapAsync(asyncFn) {
        return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
    };

    function asyncify(func) {
        return initialParams(function (args, callback) {
            var result;
            try {
                result = func.apply(this, args);
            } catch (e) {
                return callback(e);
            }
            // if result is Promise object
            if (isObject(result) && typeof result.then === ‘function‘) {
                result.then(function(value) {
                    invokeCallback(callback, null, value);
                }, function(err) {
                    invokeCallback(callback, err.message ? err : new Error(err));
                });
            } else {
                callback(null, result);
            }
        });
    };

    function isObject(value) {
        var type = typeof value;
        return value != null && (type == ‘object‘ || type == ‘function‘);
    };

    function invokeCallback(callback, error, value) {
        try {
            callback(error, value);
        } catch (e) {
            setImmediate$1(rethrow, e);
        }
    };

    // 重寫數組中的slice方法
    function slice(arrayLike, start) {    // arrayLike: 類數組對象  start: 開始位置
        start = start|0;
        var newLen = Math.max(arrayLike.length - start, 0);    // 長度
        var newArr = Array(newLen);    // 創建一個長度為newLen的數組
        for(var idx = 0; idx < newLen; idx++)  {
            newArr[idx] = arrayLike[start + idx];
        };
        return newArr;    // 返回數組
    };

    // 執行一次
    function onlyOnce(fn) {
        return function() {
            if (fn === null) throw new Error("Callback was already called.");    // 回調已被調用
            var callFn = fn;
            fn = null;
            callFn.apply(this, arguments);    //調用callFn 參數就是用戶回調函數中的參數
        };
    };

    var waterfall = function(tasks, callback) {    // tasks: 異步函數數組容器, callback: 回調
        callback = once(callback || noop);    // 回調函數
        if (!isArray(tasks)) return callback(new Error(‘First argument to waterfall must be an array of functions‘));    // 第一個參數必須是數組(函數數組)!
        if (!tasks.length) return callback();    // 空數組的話直接調用回調函數(無參數)
        var taskIndex = 0;    // 任務索引

        function nextTask(args) {    // 參數數組
            var task = wrapAsync(tasks[taskIndex++]);    // 數組中的任務
            args.push(onlyOnce(next));    // 把next方法添加到args數組中去
            task.apply(null, args);    // 調用數組中task函數(參數是數組)
        };

        function next(err/*, ...args*/) {    // 其實就是函數參數中的回調函數callback
            if (err || taskIndex === tasks.length) {    // 只要有錯誤或者函數數組任務都完成了
                return callback.apply(null, arguments);    // 就執行回調
            };
            nextTask(slice(arguments, 1));    // 數組中的函數沒循環完且沒出錯,那就繼續調用
        };

        nextTask([]);    // 調用
    };
}());
waterfall函數中有個next方法,其實我們寫的回調就是next方法。

好吧,以上代碼直接抽取async中的代碼,可以直接使用。如果只想要這一個功能的話。

async源碼學習 - waterfall函數的使用及原理實現