async源碼學習 - waterfall函數的使用及原理實現
阿新 • • 發佈:2017-05-29
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函數的使用及原理實現