├── .gitignore ├── .travis.yml ├── bower.json ├── component.json ├── examples ├── node_flow.js ├── browser_xhr.js ├── series_eachSeries.js └── demo.js ├── benchmark ├── index.js ├── then.js ├── thunks-gen.js ├── thunks.js ├── async.js ├── co.js ├── bluebird.js └── promise.js ├── package.json ├── index.d.ts ├── README.md ├── then.js └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/ 3 | debug/ 4 | 5 | npm-debug.log 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | - "8" 6 | - "9" 7 | sudo: false 8 | cache: 9 | directories: 10 | - node_modules 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thenjs", 3 | "main": "then.js", 4 | "version": "2.1.0", 5 | "description": "The fastest, smallest, fully compatible, full-featured asynchronous module!", 6 | "homepage": "https://github.com/teambition/then.js", 7 | "authors": [ 8 | "Yan Qing " 9 | ], 10 | "keywords": [ 11 | "promise", 12 | "then", 13 | "async", 14 | "then.js", 15 | "thenjs" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "benchmark", 22 | "test", 23 | "examples" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thenjs", 3 | "main": "then.js", 4 | "version": "2.1.0", 5 | "description": "The fastest, smallest, fully compatible, full-featured asynchronous module!", 6 | "homepage": "https://github.com/teambition/then.js", 7 | "authors": [ 8 | "Yan Qing " 9 | ], 10 | "keywords": [ 11 | "promise", 12 | "then", 13 | "async", 14 | "then.js", 15 | "thenjs" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "benchmark", 22 | "test", 23 | "examples" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/node_flow.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var Thenjs = require('../then.js') 5 | var fs = require('fs') 6 | 7 | function fileStats (path) { 8 | return function (callback) { 9 | fs.stat(path, callback) 10 | } 11 | } 12 | 13 | Thenjs 14 | .each(['demo.js', '../then.min.js', '../.gitignore'], function (cont, path) { 15 | console.log(111111, cont, path) 16 | fileStats(path)(cont) 17 | }) 18 | .then(function (cont, result) { 19 | console.log('Success: ', result) 20 | fileStats('none.js')(cont) 21 | }) 22 | .fail(function (cont, error) { 23 | console.error('A file path error: ', error) 24 | }) 25 | -------------------------------------------------------------------------------- /examples/browser_xhr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console, Thenjs, XMLHttpRequest */ 3 | 4 | function request (url) { 5 | return function (callback) { 6 | var xhr = new XMLHttpRequest() 7 | xhr.open('GET', url) 8 | xhr.onreadystatechange = function () { 9 | if (this.readyState !== 4) return 10 | callback(this.status === 200 ? null : xhr, xhr) 11 | } 12 | xhr.send() 13 | } 14 | } 15 | 16 | Thenjs(request('http://www.w3school.com.cn/')) 17 | .then(function (cont, data) { 18 | console.log(data) 19 | cont(null, data.responseText) 20 | }) 21 | .then(function (cont, data) { 22 | console.log(data) 23 | }) 24 | .fail(function (cont, error) { 25 | console.error(error.statusText) 26 | }) 27 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console, Promise */ 3 | 4 | var JSBench = require('jsbench') 5 | var len = 1000 // 任务队列长度 6 | var cycles = 1000 // 每个测试体运行次数 7 | var syncMode = true // 用同步任务测试 8 | 9 | var jsbench = new JSBench() 10 | 11 | console.log((syncMode ? 'Sync' : 'Async') + ' Benchmark...') 12 | 13 | jsbench 14 | .add('Promise', require('./promise.js')(len, syncMode)) 15 | .add('bluebird', require('./bluebird.js')(len, syncMode)) 16 | .add('co', require('./co.js')(len, syncMode)) 17 | .add('thunks', require('./thunks.js')(len, syncMode)) 18 | .add('thunks-generator', require('./thunks-gen.js')(len, syncMode)) 19 | .add('async', require('./async.js')(len, syncMode)) 20 | .add('thenjs', require('./then.js')(len, syncMode)) 21 | // on('cycle', function(e) {console.log(e.name, e.cycle, e.time + 'ms')}). 22 | .run(cycles) 23 | -------------------------------------------------------------------------------- /examples/series_eachSeries.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var Thenjs = require('../then.js') 5 | 6 | Thenjs(function (cont) { 7 | cont(null, [ 8 | function (cont, index) { 9 | console.log(index, 'val1') 10 | cont() 11 | }, 12 | function (cont, index) { 13 | console.log(index, 'val2') 14 | cont() 15 | } 16 | ]) 17 | }) 18 | .series() 19 | .then(function () { 20 | console.log('series1 end') 21 | }) 22 | 23 | Thenjs(function (cont) { 24 | Thenjs.series([ 25 | function (cont2, index) { 26 | console.log(index, 'val1') 27 | cont2() 28 | }, 29 | function (cont2, index) { 30 | console.log(index, 'val2') 31 | cont2() 32 | } 33 | ]).fin(cont) 34 | }) 35 | .then(function () { 36 | console.log('series2 end') 37 | }) 38 | 39 | Thenjs(function (cont) { 40 | cont(null, ['a', 'b', 'c']) 41 | }) 42 | .eachSeries(null, function (cont, value, index) { 43 | console.log(value, index) 44 | cont() 45 | }) 46 | .then(function () { 47 | console.log('eachSeries end') 48 | }) 49 | -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var Thenjs = require('../then.js') 5 | 6 | function task (arg, callback) { // 模拟异步任务 7 | Thenjs.nextTick(function () { 8 | callback(null, arg) 9 | }) 10 | } 11 | 12 | Thenjs(function (cont) { 13 | task(10, cont) 14 | }) 15 | .then(function (cont, arg) { 16 | console.log(arg) 17 | cont(new Error('error!'), 123) 18 | }) 19 | .fin(function (cont, error, result) { 20 | console.log(error, result) 21 | cont() 22 | }) 23 | .each([0, 1, 2], function (cont, value) { 24 | task(value * 2, cont) // 并行执行队列任务,把队列 list 中的每一个值输入到 task 中运行 25 | }) 26 | .then(function (cont, result) { 27 | console.log(result) 28 | cont() 29 | }) 30 | .series([ // 串行执行队列任务 31 | function (cont) { task(88, cont) }, // 队列第一个是异步任务 32 | function (cont) { cont(null, 99) } // 第二个是同步任务 33 | ]) 34 | .then(function (cont, result) { 35 | console.log(result) 36 | cont(new Error('error!!')) 37 | }) 38 | .fail(function (cont, error) { // 通常应该在链的最后放置一个 `fail` 方法收集异常 39 | console.log(error) 40 | console.log('DEMO END!') 41 | }) 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thenjs", 3 | "version": "2.1.0", 4 | "description": "The fastest, smallest, fully compatible, full-featured asynchronous module!", 5 | "authors": [ 6 | "Yan Qing " 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/teambition/then.js" 11 | }, 12 | "main": "then.js", 13 | "typings": "index.d.ts", 14 | "scripts": { 15 | "test": "standard && tman", 16 | "bench": "node benchmark/index" 17 | }, 18 | "homepage": "https://github.com/teambition/then.js", 19 | "keywords": [ 20 | "promise", 21 | "then", 22 | "async", 23 | "then.js", 24 | "thenjs" 25 | ], 26 | "tags": [ 27 | "promise", 28 | "then", 29 | "async", 30 | "then.js", 31 | "thenjs" 32 | ], 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "async": "^2.6.0", 36 | "bluebird": "^3.5.1", 37 | "co": "^4.6.0", 38 | "jsbench": "^1.2.x", 39 | "standard": "^11.0.1", 40 | "thunks": "^4.9.2", 41 | "tman": "^1.7.4" 42 | }, 43 | "files": [ 44 | "README.md", 45 | "then.js", 46 | "index.d.ts" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /benchmark/then.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global */ 3 | 4 | var Thenjs = require('../then') 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x, callback) { 13 | callback(null, x) 14 | } 15 | } else { // 模拟异步任务 16 | task = function (x, callback) { 17 | setImmediate(function () { 18 | callback(null, x) 19 | }) 20 | } 21 | } 22 | 23 | function toThunk (fn, x) { 24 | return function (done) { 25 | fn(x, done) 26 | } 27 | } 28 | 29 | // 构造任务队列 30 | for (var i = 0; i < len; i++) { 31 | list[i] = i 32 | tasks[i] = task 33 | } 34 | 35 | return function (callback) { 36 | // Thenjs 测试主体 37 | Thenjs.each(list, function (cont, i) { // 并行 list 队列 38 | task(i, cont) 39 | }) 40 | .eachSeries(list, function (cont, i) { // 串行 list 队列 41 | task(i, cont) 42 | }) 43 | .parallel(tasks.map(toThunk)) // 并行 tasks 队列 44 | .series(tasks.map(toThunk)) // 串行 tasks 队列 45 | .fin(function (cont, error) { 46 | callback(error) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /benchmark/thunks-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var thunk = require('thunks')() 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x) { 13 | return function (callback) { 14 | callback(null, x) 15 | } 16 | } 17 | } else { // 模拟异步任务 18 | task = function (x) { 19 | return function (callback) { 20 | setImmediate(function () { 21 | callback(null, x) 22 | }) 23 | } 24 | } 25 | } 26 | 27 | // 构造任务队列 28 | for (var i = 0; i < len; i++) { 29 | list[i] = i 30 | tasks[i] = task 31 | } 32 | 33 | return function (callback) { 34 | // Thunk generator 测试主体 35 | thunk(function * () { 36 | // 并行 list 队列 37 | yield list.map(function (i) { 38 | return task(i) 39 | }) 40 | // 串行 list 队列 41 | for (var i = 0, l = list.length; i < l; i++) { 42 | yield task(i) 43 | } 44 | // 并行 tasks 队列 45 | yield tasks.map(function (task, i) { 46 | return task(i) 47 | }) 48 | // 串行 tasks 队列 49 | for (i = 0, l = tasks.length; i < l; i++) { 50 | yield tasks[i](i) 51 | } 52 | })(callback) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /benchmark/thunks.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global Promise */ 3 | 4 | var thunk = require('thunks')() 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x) { 13 | return function (callback) { 14 | callback(null, x) 15 | } 16 | } 17 | } else { // 模拟异步任务 18 | task = function (x) { 19 | return function (callback) { 20 | setImmediate(function () { 21 | callback(null, x) 22 | }) 23 | } 24 | } 25 | } 26 | 27 | // 构造任务队列 28 | for (var i = 0; i < len; i++) { 29 | list[i] = i 30 | tasks[i] = task 31 | } 32 | 33 | return function (callback) { 34 | // Thunk 测试主体 35 | thunk.all(list.map(function (i) { // 并行 list 队列 36 | return task(i) 37 | }))(function () { // 串行 tasks 队列 38 | return thunk.seq(list.map(function (i) { 39 | return task(i) 40 | })) 41 | })(function () { 42 | return thunk.all(tasks.map(function (sunTask, i) { // 并行 tasks 队列 43 | return sunTask(i) 44 | })) 45 | })(function () { // 串行 tasks 队列 46 | return thunk.seq(tasks.map(function (sunTask, i) { // 并行 tasks 队列 47 | return sunTask(i) 48 | })) 49 | })(callback) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/async.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global */ 3 | 4 | var async = require('async') 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x, callback) { 13 | callback(null, x) 14 | } 15 | } else { // 模拟异步任务 16 | task = function (x, callback) { 17 | setImmediate(function () { 18 | callback(null, x) 19 | }) 20 | } 21 | } 22 | 23 | function toThunk (fn, x) { 24 | return function (done) { 25 | fn(x, done) 26 | } 27 | } 28 | 29 | // 构造任务队列 30 | for (var i = 0; i < len; i++) { 31 | list[i] = i 32 | tasks[i] = task 33 | } 34 | 35 | return function (callback) { 36 | // async 测试主体 37 | async.each(list, function (i, next) { // 并行 list 队列 38 | task(i, next) 39 | }, function (err) { 40 | if (err) return callback(err) 41 | async.eachSeries(list, function (i, next) { // 串行 list 队列 42 | task(i, next) 43 | }, function (err) { 44 | if (err) return callback(err) 45 | async.parallel(tasks.map(toThunk), function (err) { // 并行 tasks 队列 46 | if (err) return callback(err) 47 | async.series(tasks.map(toThunk), callback) // 串行 tasks 队列 48 | }) 49 | }) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /benchmark/co.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var co = require('co') 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x) { 13 | return new Promise(function (resolve, reject) { 14 | resolve(x) 15 | }) 16 | } 17 | } else { // 模拟异步任务 18 | task = function (x) { 19 | return new Promise(function (resolve, reject) { 20 | setImmediate(function () { 21 | resolve(x) 22 | }) 23 | }) 24 | } 25 | } 26 | 27 | // 构造任务队列 28 | for (var i = 0; i < len; i++) { 29 | list[i] = i 30 | tasks[i] = task 31 | } 32 | 33 | return function (callback) { 34 | // co 测试主体 35 | co(function * () { 36 | // 并行 list 队列 37 | yield list.map(function (i) { 38 | return task(i) 39 | }) 40 | // 串行 list 队列 41 | for (var i = 0, l = list.length; i < l; i++) { 42 | yield task(i) 43 | } 44 | // 并行 tasks 队列 45 | yield tasks.map(function (subtask, i) { 46 | return subtask(i) 47 | }) 48 | // 串行 tasks 队列 49 | for (i = 0, l = tasks.length; i < l; i++) { 50 | yield tasks[i](i) 51 | } 52 | }).then(function () { 53 | callback() 54 | }).catch(callback) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /benchmark/bluebird.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global console */ 3 | 4 | var Bluebird = require('bluebird') 5 | 6 | module.exports = function (len, syncMode) { 7 | var task 8 | var list = [] 9 | var tasks = [] 10 | 11 | if (syncMode) { // 模拟同步任务 12 | task = function (x) { 13 | return new Bluebird(function (resolve, reject) { 14 | resolve(x) 15 | }) 16 | } 17 | } else { // 模拟异步任务 18 | task = function (x) { 19 | return new Bluebird(function (resolve, reject) { 20 | setImmediate(function () { 21 | resolve(x) 22 | }) 23 | }) 24 | } 25 | } 26 | 27 | // 构造任务队列 28 | for (var i = 0; i < len; i++) { 29 | list[i] = i 30 | tasks[i] = task 31 | } 32 | 33 | return function (callback) { 34 | // bluebird 测试主体 35 | Bluebird 36 | .map(list, function (i) { // 并行 list 队列 37 | return task(i) 38 | }) 39 | .then(function () { // 串行 list 队列 40 | return Bluebird.reduce(list, function (x, i) { 41 | return task(i) 42 | }, 1) 43 | }) 44 | .then(function () { // 并行 tasks 队列 45 | return Bluebird.all(tasks.map(function (task, i) { 46 | return task(i) 47 | })) 48 | }) 49 | .then(function () { // 串行 tasks 队列 50 | return tasks.reduce(function (promise, subTask, i) { 51 | return promise.then(function () { 52 | return subTask(i) 53 | }) 54 | }, Bluebird.resolve()) 55 | }) 56 | .then(function () { 57 | return callback() 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global Promise */ 3 | 4 | module.exports = function (len, syncMode) { 5 | var task 6 | var list = [] 7 | var tasks = [] 8 | 9 | if (syncMode) { // 模拟同步任务 10 | task = function (x) { 11 | return new Promise(function (resolve, reject) { 12 | resolve(x) 13 | }) 14 | } 15 | } else { // 模拟异步任务 16 | task = function (x) { 17 | return new Promise(function (resolve, reject) { 18 | setImmediate(function () { 19 | resolve(x) 20 | }) 21 | }) 22 | } 23 | } 24 | 25 | // 构造任务队列 26 | for (var i = 0; i < len; i++) { 27 | list[i] = i 28 | tasks[i] = task 29 | } 30 | 31 | return function (callback) { 32 | // 原生 Promise 测试主体 33 | Promise.all(list.map(function (i) { // 并行 list 队列 34 | return task(i) 35 | })) 36 | .then(function () { // 串行 list 队列 37 | return list.reduce(function (promise, i) { 38 | return promise.then(function () { 39 | return task(i) 40 | }) 41 | }, Promise.resolve()) 42 | }) 43 | .then(function () { // 并行 tasks 队列 44 | return Promise.all(tasks.map(function (subTask, i) { 45 | return subTask(i) 46 | })) 47 | }) 48 | .then(function () { // 串行 tasks 队列 49 | return tasks.reduce(function (promise, subTask, i) { 50 | return promise.then(function () { 51 | return subTask(i) 52 | }) 53 | }, Promise.resolve()) 54 | }) 55 | .then(function () { 56 | callback() 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface IThenjsConstructor { 2 | /** 3 | * start: Function,function (cont) {}, 即 IThunk 函数(见下面解释),或者 Promise 对象,或者 Thenjs 对象,或者其他值。 4 | * debug: Boolean 或 Function,可选,开启调试模式,将每一个链的运行结果用 debug 函数处理,如果debug为非函数真值,则调用 console.log,下同 5 | */ 6 | (start?: IstartFn, debug?: Boolean): IThenjsProto 7 | each: Ieach 8 | eachSeries: IeachSeries 9 | parallel: Iparallel 10 | series: Iseries 11 | parallelLimit: IparallelLimit 12 | eachLimit: IeachLimit 13 | nextTick: InextTick 14 | defer: Idefer 15 | onerror: Ionerror 16 | } 17 | 18 | interface IThenjsProto { 19 | each?: Ieach 20 | eachSeries?: IeachSeries 21 | parallel?: Iparallel 22 | series?: Iseries 23 | parallelLimit?: IparallelLimit 24 | eachLimit?: IeachLimit 25 | then(successCallback?: (cont?: IstartFn, ...results: any[]) => any, errorCallback?: (cont?: IstartFn, error?: Error) => any): IThenjsProto 26 | fin(finallyHandler: (cont?: IstartFn, error?: Error, ...results: any[]) => any | IstartFn): IThenjsProto 27 | finally(finallyHandler: (cont?: IstartFn, error?: Error, ...results: any[]) => any | IstartFn): IThenjsProto 28 | fail(errorHandler: (cont?: IstartFn, error?: Error) => any | IstartFn): IThenjsProto 29 | catch(errorHandler: (cont?: IstartFn, error?: Error) => any | IstartFn): IThenjsProto 30 | toIThunk(): IThunk 31 | } 32 | 33 | interface Ieach { 34 | (array: T[] | IArrayLike, iterator: (cont?: IstartFn, value?: T, index?: Number, array?: T[] | IArrayLike) => any, debug?: Boolean): IThenjsProto 35 | } 36 | 37 | interface IeachSeries { 38 | (array: T[] | IArrayLike, iterator: (cont?: IstartFn, value?: T, index?: Number, array?: T[] | IArrayLike) => any, debug?: Boolean): IThenjsProto 39 | } 40 | 41 | interface Iparallel { 42 | (tasksArray: ((cont: IThunk) => IThenjsProto)[], debug?: Boolean): IThenjsProto 43 | } 44 | 45 | interface Iseries { 46 | (tasksArray: IThunk[]): IThenjsProto 47 | } 48 | 49 | interface IparallelLimit { 50 | (tasksArray: IThunk[], limit: Number, debug?: Boolean): IThenjsProto 51 | } 52 | 53 | interface IeachLimit { 54 | (array: T[] | IArrayLike, iterator: (cont?: IstartFn, value?: T, index?: Number, array?: T[] | IArrayLike) => any, limit: Number, debug?: Boolean): IThenjsProto 55 | } 56 | 57 | interface IstartFn { 58 | (...args: any[]): IThunk | IThenjsConstructor | IPromiseLike | IPromise | Function | any 59 | } 60 | 61 | interface IThunk { 62 | (error?: any, result?: any): any 63 | } 64 | 65 | interface InextTick { 66 | (callback: (...args: any[]) => any, ...args: any[]): any 67 | } 68 | 69 | interface Idefer { 70 | (errorHandler: (error: Error) => any, callback: (...args: any[]) => any, ...args: any[]): any 71 | } 72 | 73 | interface Ionerror { 74 | (error: Error): void 75 | } 76 | 77 | interface IPromiseLike { 78 | /** 79 | * Attaches callbacks for the resolution and/or rejection of the Promise. 80 | * @param onfulfilled The callback to execute when the Promise is resolved. 81 | * @param onrejected The callback to execute when the Promise is rejected. 82 | * @returns A Promise for the completion of which ever callback is executed. 83 | */ 84 | then(onfulfilled?: (value: T) => TResult | IPromiseLike, onrejected?: (reason: any) => TResult | IPromiseLike): IPromiseLike 85 | then(onfulfilled?: (value: T) => TResult | IPromiseLike, onrejected?: (reason: any) => void): IPromiseLike 86 | } 87 | 88 | /** 89 | * Represents the completion of an asynchronous operation 90 | */ 91 | interface IPromise { 92 | /** 93 | * Attaches callbacks for the resolution and/or rejection of the Promise. 94 | * @param onfulfilled The callback to execute when the Promise is resolved. 95 | * @param onrejected The callback to execute when the Promise is rejected. 96 | * @returns A Promise for the completion of which ever callback is executed. 97 | */ 98 | then(onfulfilled?: (value: T) => TResult | IPromiseLike, onrejected?: (reason: any) => TResult | IPromiseLike): IPromise 99 | then(onfulfilled?: (value: T) => TResult | IPromiseLike, onrejected?: (reason: any) => void): IPromise 100 | 101 | /** 102 | * Attaches a callback for only the rejection of the Promise. 103 | * @param onrejected The callback to execute when the Promise is rejected. 104 | * @returns A Promise for the completion of the callback. 105 | */ 106 | catch(onrejected?: (reason: any) => T | IPromiseLike): IPromise 107 | } 108 | 109 | interface IArrayLike { 110 | length: number 111 | [n: number]: T 112 | } 113 | 114 | declare var Thenjs: IThenjsConstructor 115 | 116 | export default Thenjs 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | then.js 2 | ==== 3 | 史上最快,与 node callback 完美结合的异步流程控制库! 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build Status][travis-image]][travis-url] 7 | 8 | **能用简单优美的方式将任何同步或异步回调函数转换成then()链式调用!** 9 | 10 | **你可以在服务器端(node.js)或浏览器中使用then.js,兼容ie6/7/8。** 11 | 12 | ## 特征 13 | 14 | 1. 可以像标准的 `Promise` 那样,把N多异步回调函数写成一个长长的 `then` 链,并且比 Promise 更简洁自然。因为如果使用标准 Promise 的 then 链,其中的异步函数都必须转换成 Promise,Thenjs 则无需转换,像使用 callback 一样执行异步函数即可。 15 | 16 | 2. 可以像 `async`那样实现同步或异步队列函数,并且比 async 更方便。因为 async 的队列是一个个独立体,而 Thenjs 的队列在 `Thenjs` 链上,可形成链式调用。 17 | 18 | 3. 强大的 Error 机制,可以捕捉任何同步或异步的异常错误,甚至是位于异步函数中的语法错误。并且捕捉的错误任君处置。 19 | 20 | 4. 开启debug模式,可以把每一个then链运行结果输出到debug函数(未定义debug函数则用 console.log),方便调试。 21 | 22 | ## Thunk 23 | 24 | **`thunk`** 是一个被封装了同步或异步任务的函数,这个函数有唯一一个参数 `callback`。运行 **`thunk`**后,当其封装的任务执行完毕时,任务结果会输入 `callback` 执行。`callback` 的第一个参数是 `error`,没有发生 `error` 则为 `null`。 25 | 26 | **推荐使用新一代异步流程控制库 [thunks](https://github.com/thunks/thunks),它是比 `co` 更强大的存在!** 27 | 28 | ##Benchmark 29 | 30 | **模拟异步测试:** 31 | 32 | ```bash 33 | ➜ then.js git:(master) ✗ node --harmony benchmark/index 34 | Async Benchmark... 35 | 36 | JSBench Start (1000 cycles, async mode): 37 | Test Promise... 38 | Test co... 39 | Test thunks-generator... 40 | Test bluebird... 41 | Test when... 42 | Test RSVP... 43 | Test async... 44 | Test thenjs... 45 | Test thunks... 46 | 47 | JSBench Results: 48 | co: 1000 cycles, 32.621 ms/cycle, 30.655 ops/sec 49 | Promise: 1000 cycles, 30.807 ms/cycle, 32.460 ops/sec 50 | when: 1000 cycles, 28.828 ms/cycle, 34.688 ops/sec 51 | thunks: 1000 cycles, 17.402 ms/cycle, 57.465 ops/sec 52 | RSVP: 1000 cycles, 10.358 ms/cycle, 96.544 ops/sec 53 | thunks-generator: 1000 cycles, 9.822 ms/cycle, 101.812 ops/sec 54 | bluebird: 1000 cycles, 8.54 ms/cycle, 117.096 ops/sec 55 | async: 1000 cycles, 6.54 ms/cycle, 152.905 ops/sec 56 | thenjs: 1000 cycles, 5.085 ms/cycle, 196.657 ops/sec 57 | 58 | co: 100%; Promise: 105.89%; when: 113.16%; thunks: 187.46%; RSVP: 314.94%; thunks-generator: 332.12%; bluebird: 381.98%; async: 498.79%; thenjs: 641.51%; 59 | 60 | JSBench Completed! 61 | ``` 62 | 63 | **模拟异步测试:** 64 | 65 | ```bash 66 | ➜ then.js git:(master) ✗ node --harmony benchmark/index 67 | Sync Benchmark... 68 | 69 | JSBench Start (1000 cycles, async mode): 70 | Test Promise... 71 | Test co... 72 | Test thunks-generator... 73 | Test bluebird... 74 | Test when... 75 | Test RSVP... 76 | Test async... 77 | Test thenjs... 78 | Test thunks... 79 | 80 | JSBench Results: 81 | co: 1000 cycles, 26.342 ms/cycle, 37.962 ops/sec 82 | Promise: 1000 cycles, 25.662 ms/cycle, 38.968 ops/sec 83 | when: 1000 cycles, 21.36 ms/cycle, 46.816 ops/sec 84 | thunks: 1000 cycles, 5.242 ms/cycle, 190.767 ops/sec 85 | thunks-generator: 1000 cycles, 5.073 ms/cycle, 197.122 ops/sec 86 | async: 1000 cycles, 2.806 ms/cycle, 356.379 ops/sec 87 | RSVP: 1000 cycles, 2.27 ms/cycle, 440.529 ops/sec 88 | bluebird: 1000 cycles, 1.722 ms/cycle, 580.720 ops/sec 89 | thenjs: 1000 cycles, 1.324 ms/cycle, 755.287 ops/sec 90 | 91 | co: 100%; Promise: 102.65%; when: 123.32%; thunks: 502.52%; thunks-generator: 519.26%; async: 938.77%; RSVP: 1160.44%; bluebird: 1529.73%; thenjs: 1989.58%; 92 | 93 | JSBench Completed! 94 | ``` 95 | 96 | **`async` 不支持过长(如超过3000)的同步任务(将会出现`Maximum call stack size exceeded`)** 97 | 98 | ## Demo 99 | 100 | ```js 101 | 'use strict'; 102 | /*global console*/ 103 | 104 | var Thenjs = require('../then.js'); 105 | 106 | function task(arg, callback) { // 模拟异步任务 107 | Thenjs.nextTick(function () { 108 | callback(null, arg); 109 | }); 110 | } 111 | 112 | Thenjs(function (cont) { 113 | task(10, cont); 114 | }) 115 | .then(function (cont, arg) { 116 | console.log(arg); 117 | cont(new Error('error!'), 123); 118 | }) 119 | .fin(function (cont, error, result) { 120 | console.log(error, result); 121 | cont(); 122 | }) 123 | .each([0, 1, 2], function (cont, value) { 124 | task(value * 2, cont); // 并行执行队列任务,把队列 list 中的每一个值输入到 task 中运行 125 | }) 126 | .then(function (cont, result) { 127 | console.log(result); 128 | cont(); 129 | }) 130 | .series([ // 串行执行队列任务 131 | function (cont) { task(88, cont); }, // 队列第一个是异步任务 132 | function (cont) { cont(null, 99); } // 第二个是同步任务 133 | ]) 134 | .then(function (cont, result) { 135 | console.log(result); 136 | cont(new Error('error!!')); 137 | }) 138 | .fail(function (cont, error) { // 通常应该在链的最后放置一个 `fail` 方法收集异常 139 | console.log(error); 140 | console.log('DEMO END!'); 141 | }); 142 | ``` 143 | 144 | ## Install 145 | 146 | **Node.js:** 147 | 148 | npm install thenjs 149 | 150 | **bower:** 151 | 152 | bower install thenjs 153 | 154 | **Browser:** 155 | 156 | 157 | 158 | 159 | ## API 160 | 161 | **以下所有的 'cont',取义于 `continue`。'cont' 绑定到了下一个 `Thenjs` 链,即收集当前任务结果,继续执行下一链。它等效于 node.js 的 `callback`,可以接受多个参数,其中第一个参数为 'error'。** 162 | 163 | ### Thenjs(start, [debug]) 164 | 165 | 主构造函数,返回一个新的 `Thenjs` 对象。 166 | 167 | + **start:** Function,function (cont) {}, 即 `thunk` 函数(见下面解释),或者 `Promise` 对象,或者 `Thenjs` 对象,或者其他值。 168 | + **debug:** Boolean 或 Function,可选,开启调试模式,将每一个链的运行结果用 `debug` 函数处理,如果debug为非函数真值,则调用 `console.log`,下同 169 | 170 | ```js 171 | Thenjs().then(function(res) {}); 172 | ``` 173 | 174 | ```js 175 | Thenjs(123).then(function(res) {}); 176 | ``` 177 | 178 | ```js 179 | Thenjs(promise).then(function(res) {}); 180 | ``` 181 | 182 | ```js 183 | Thenjs(function(cont) { cont(result); }).then(function(res) {}); 184 | ``` 185 | 186 | ### Thenjs.each(array, iterator, [debug]) 187 | 188 | 将 `array` 中的值应用于 `iterator` 函数(同步或异步),并行执行。返回一个新的 `Thenjs` 对象。 189 | 190 | + **array:** Array 或 类数组 191 | + **iterator:** Function,function (cont, value, index, array) {} 192 | 193 | ```js 194 | Thenjs.each([0, 1, 2], function (cont, value) { 195 | task(value * 2, cont); 196 | }) 197 | .then(function (cont, result) { 198 | console.log(result); 199 | }); 200 | ``` 201 | 202 | ### Thenjs.eachSeries(array, iterator, [debug]) 203 | 204 | 将 `array` 中的值应用于 `iterator` 函数(同步或异步),串行执行。返回一个新的 `Thenjs` 对象。 205 | 206 | + **array:** Array 或 类数组 207 | + **iterator:** Function,function (cont, value, index, array) {} 208 | 209 | ```js 210 | Thenjs.eachSeries([0, 1, 2], function (cont, value) { 211 | task(value * 2, cont); 212 | }) 213 | .then(function (cont, result) { 214 | console.log(result); 215 | }); 216 | ``` 217 | 218 | ### Thenjs.parallel(tasksArray, [debug]) 219 | 220 | `tasksArray` 是一个函数(同步或异步)数组,并行执行。返回一个新的 `Thenjs` 对象。 221 | 222 | + **tasksArray:** Array,[taskFn1, taskFn2, taskFn3, ...],其中,taskFn 形式为 function (cont) {} 223 | 224 | ```js 225 | Thenjs.parallel([ 226 | function (cont) { task(88, cont); }, 227 | function (cont) { cont(null, 99); } 228 | ]) 229 | .then(function (cont, result) { 230 | console.log(result); 231 | }); 232 | ``` 233 | 234 | ### Thenjs.series(tasksArray, [debug]) 235 | 236 | `tasksArray` 是一个函数(同步或异步)数组,串行执行。返回一个新的 `Thenjs` 对象。 237 | 238 | + **tasksArray:** Array,[taskFn1, taskFn2, taskFn3, ...],其中,taskFn 形式为 function (cont) {} 239 | 240 | ```js 241 | Thenjs.series([ 242 | function (cont) { task(88, cont); }, 243 | function (cont) { cont(null, 99); } 244 | ]) 245 | .then(function (cont, result) { 246 | console.log(result); 247 | }); 248 | ``` 249 | 250 | + taskFn 形式为 function(cont,result){} 时,通过result可获取之前项的返回值 251 | 252 | ```js 253 | Thenjs.series([ 254 | function (cont,result) { typeof result === 'undefined'; cont(null,100) }, 255 | function (cont,result) { assert.equal(100,result[0]);cont(null, 99); }, 256 | function (cont,result) { assert.equal(100,result[0]); assert.equal(99,result[1]);cont(null, 98); } 257 | ]) 258 | .then(function (cont, result) { 259 | console.log(result); 260 | }); 261 | 262 | ``` 263 | 264 | ### Thenjs.parallelLimit(tasksArray, limit, [debug]) 265 | 266 | `tasksArray` 是一个函数(同步或异步)数组,并行执行,最大并行数量为 `limit`,当并行队列中某一项完成时,会立即补上,也就是说,并发数会一直保持在 `limit`,除非待运行任务不足。返回一个新的 `Thenjs` 对象。 267 | 268 | + **tasksArray:** Array,[taskFn1, taskFn2, taskFn3, ...],其中,taskFn 形式为 function (cont) {} 269 | + **limit:** Number, 应该大于 0 的整数,取 0 则无限制 270 | 271 | ```js 272 | Thenjs.parallel([task1, task2, ..., taskN], 10) 273 | .then(function (cont, result) { 274 | console.log(result); 275 | }); 276 | ``` 277 | 278 | ### Thenjs.eachLimit(array, iterator, limit, [debug]) 279 | 280 | 类似于上,但将 `array` 中的值应用于 `iterator` 函数(同步或异步),并行执行,最大并行数量为 `limit`。返回一个新的 `Thenjs` 对象。 281 | 282 | + **array:** Array 或 类数组 283 | + **iterator:** Function,function (cont, value, index, array) {} 284 | + **limit:** Number, 应该大于 0 的整数,取 0 则无限制 285 | 286 | ```js 287 | Thenjs.eachLimit([1, 2, ..., n], function (cont, value) { 288 | task(value * 2, cont); 289 | }, 10) 290 | .then(function (cont, result) { 291 | console.log(result); 292 | }); 293 | ``` 294 | 295 | ### Thenjs.prototype.then(successHandler, [errorHandler]) 296 | 297 | 如果上一链正确,则进入 `successHandler` 执行,否则进入 `errorHandler` 执行。返回一个新的 `Thenjs` 对象。 298 | 299 | + **successHandler:** Function,function (cont, value1, value2, ...) {} 300 | + **errorHandler:** Function,可选,function (cont, error) {} 301 | 302 | ```js 303 | Thenjs(function (cont) { 304 | task(10, cont); 305 | }) 306 | .then(function (cont, arg) { 307 | console.log(arg); 308 | }, function (cont, error) { 309 | console.error(error); 310 | }); 311 | ``` 312 | 313 | ### Thenjs.prototype.finally(finallyHandler) 314 | 315 | 别名:Thenjs.prototype.fin(finallyHandler) 316 | 317 | 原名`all`已停用。 318 | 319 | 无论上一链是否存在 `error`,均进入 `finallyHandler` 执行,等效于 `.then(successHandler, errorHandler)`。返回一个新的 `Thenjs` 对象。 320 | 321 | + **finallyHandler:** Function,function (cont, error, value1, value2, ...) {} 322 | 323 | `finallyHandler` 也可以是外层的 `cont` 哦,如果是 `cont`, 则不会被注入本层的 cont, 所以,可以这样嵌套用: 324 | 325 | ```js 326 | Thenjs(function (cont) { 327 | task(10, cont); 328 | }) 329 | .then(function (cont, arg) { 330 | console.log(arg); 331 | Thenjs(function (cont2) { 332 | task(10, cont2); 333 | }) 334 | .then(function (cont2, arg) { 335 | console.log(arg); 336 | cont2(new Error('error!'), 123); 337 | }) 338 | .fin(cont); 339 | }) 340 | .fin(function (cont, error, result) { 341 | console.log(error, result); 342 | cont(); 343 | }); 344 | ``` 345 | 在复杂的异步组合中是很有用的。 346 | 347 | 348 | ### Thenjs.prototype.fail(errorHandler) 349 | 350 | 别名:Thenjs.prototype.catch(errorHandler) 351 | 352 | `fail` 用于捕捉 `error`,如果在它之前的任意一个链上产生了 `error`,并且未被 `then`, `finally` 等捕获,则会跳过中间链,直接进入 `fail`。返回一个新的 `Thenjs` 对象。 353 | 354 | + **errorHandler:** Function,function (cont, error) {} 355 | 356 | 类似 `.finally(finallyHandler)` ,这里的 `errorHandler` 也可以是 `cont` : 357 | 358 | ```js 359 | Thenjs(function (cont) { 360 | task(10, cont); 361 | }) 362 | .then(function (cont, arg) { 363 | console.log(arg); 364 | Thenjs(function (cont2) { 365 | task(10, cont2); 366 | }) 367 | .then(function (cont2, arg) { 368 | console.log(arg); 369 | cont2(new Error('error!'), 123); 370 | }) 371 | .fail(cont); 372 | }) 373 | .then(function (cont, result) { 374 | console.log(result); 375 | }) 376 | .fail(function (cont, error) { 377 | console.error(error); 378 | }); 379 | ``` 380 | 381 | ### Thenjs.prototype.each(array, iterator) 382 | 383 | 参数类似 `Thenjs.each`,返回一个新的 `Thenjs` 对象。 384 | 385 | 不同在于,参数可省略,如果没有参数,则会查找上一个链的输出结果作为参数,即上一个链可以这样 `cont(null, array, iterator)` 传输参数到each。下面三个队列方法行为类似。 386 | 387 | ### Thenjs.prototype.eachSeries(array, iterator) 388 | 389 | 参数类似 `Thenjs.eachSeries`,返回一个新的 `Thenjs` 对象。 390 | 391 | ### Thenjs.prototype.parallel(tasksArray) 392 | 393 | 参数类似 `Thenjs.parallel`,返回一个新的 `Thenjs` 对象。 394 | 395 | ### Thenjs.prototype.series(tasksArray) 396 | 397 | 参数类似 `Thenjs.series`,返回一个新的 `Thenjs` 对象。 398 | 399 | ### Thenjs.prototype.parallelLimit(tasksArray, limit) 400 | 401 | 参数类似 `Thenjs.parallelLimit`,返回一个新的 `Thenjs` 对象。 402 | 403 | ### Thenjs.prototype.eachLimit(array, iterator, limit) 404 | 405 | 参数类似 `Thenjs.eachLimit`,返回一个新的 `Thenjs` 对象。 406 | 407 | ### Thenjs.prototype.toThunk()(callback) 408 | 409 | 返回 `thunk` 函数。将 `Thenjs` 对象变成一个 `thunk`, 当 `Thenjs` 对象任务执行完毕后,结果会进入 `callback` 执行。`callback` 的第一个参数仍然是 `error`。 410 | 411 | ```js 412 | var thunk = Thenjs(function (cont) { 413 | task(10, cont); 414 | }) 415 | .then(function (cont, arg) { 416 | console.log(arg); 417 | Thenjs(function (cont2) { 418 | task(10, cont2); 419 | }) 420 | .then(function (cont2, arg) { 421 | console.log(arg); 422 | cont2(new Error('error!'), 123); 423 | }) 424 | .fail(cont); 425 | }) 426 | .then(function (cont, result) { 427 | console.log(result); 428 | }) 429 | .toThunk(); 430 | 431 | thunk(function (error, result) { 432 | console.log(error, result); 433 | }); 434 | ``` 435 | 436 | ### Thenjs.nextTick(callback, arg1, arg2, ...) 437 | 438 | 工具函数,类似于 `node.js` 的 `setImmediate`,异步执行 `callback`,而 `arg1`, `arg2` 会成为它的运行参数。 439 | 440 | ### Thenjs.defer(errorHandler, callback, arg1, arg2, ...) 441 | 442 | 工具函数,类似于 `Thenjs.nextTick`,不同的是异步使用 `try catch` 执行 `callback`,如果捕捉到 `error`,则进入 `errorHandler` 执行。 443 | 444 | + **errorHandler:** Function,function (error) {} 445 | 446 | ### Thenjs.onerror = function (error) {}; 447 | 448 | 全局配置参数,用户可自定义的全局 error 监听函数,`Thenjs.onerror` 默认值为 `undefined`。若定义,当执行链上发生 `error` 且没有被捕捉时,`error` 会进入 `Thenjs.onerror`。 449 | 450 | 451 | ### Who Used 452 | 453 | + AngularJS中文社区: 454 | + Teambition: 455 | 456 | 457 | ## Examples 458 | 459 | 更多使用案例请参考[jsGen](https://github.com/zensh/jsgen)源代码! 460 | 461 | [npm-url]: https://npmjs.org/package/thenjs 462 | [npm-image]: http://img.shields.io/npm/v/thenjs.svg 463 | 464 | [travis-url]: https://travis-ci.org/teambition/then.js 465 | [travis-image]: http://img.shields.io/travis/teambition/then.js.svg 466 | -------------------------------------------------------------------------------- /then.js: -------------------------------------------------------------------------------- 1 | // **Github:** https://github.com/teambition/then.js 2 | // 3 | // **License:** MIT 4 | 5 | /* global module, define, setImmediate, console */ 6 | ;(function (root, factory) { 7 | 'use strict' 8 | 9 | if (typeof module === 'object' && typeof module.exports === 'object') { 10 | /** 11 | * @type {{then:function}} 12 | */ 13 | module.exports = factory() 14 | } else if (typeof define === 'function' && define.amd) { 15 | define([], factory) 16 | } else { 17 | root.Thenjs = factory() 18 | } 19 | }(typeof window === 'object' ? window : this, function () { 20 | 'use strict' 21 | 22 | var maxTickDepth = 100 23 | var toString = Object.prototype.toString 24 | var hasOwnProperty = Object.prototype.hasOwnProperty 25 | // Save timer references to avoid other module (Sinon) interfering. 26 | var $setTimeout = setTimeout 27 | /* istanbul ignore next */ 28 | var nextTick = typeof setImmediate === 'function' 29 | ? setImmediate : typeof Promise === 'function' 30 | ? function (fn) { Promise.resolve().then(fn) } : function (fn) { $setTimeout(fn, 0) } 31 | var isArray = Array.isArray || function (obj) { 32 | return toString.call(obj) === '[object Array]' 33 | } 34 | 35 | // 将 `arguments` 转成数组,效率比 `[].slice.call` 高很多 36 | function slice (args, start) { 37 | start = start || 0 38 | if (start >= args.length) return [] 39 | var len = args.length 40 | var ret = Array(len - start) 41 | while (len-- > start) ret[len - start] = args[len] 42 | return ret 43 | } 44 | 45 | function map (array, iterator) { 46 | var res = [] 47 | for (var i = 0, len = array.length; i < len; i++) res.push(iterator(array[i], i, array)) 48 | return res 49 | } 50 | 51 | // 同步执行函数,同时捕捉异常 52 | function carry (errorHandler, fn) { 53 | try { 54 | fn.apply(null, slice(arguments, 2)) 55 | } catch (error) { 56 | errorHandler(error) 57 | } 58 | } 59 | 60 | // 异步执行函数,同时捕捉异常 61 | function defer (errorHandler, fn) { 62 | var args = arguments 63 | nextTick(function () { 64 | carry.apply(null, args) 65 | }) 66 | } 67 | 68 | function toThunk (object) { 69 | if (object == null) return object 70 | if (typeof object.toThunk === 'function') return object.toThunk() 71 | if (typeof object.then === 'function') { 72 | return function (callback) { 73 | object.then(function (res) { 74 | callback(null, res) 75 | }, callback) 76 | } 77 | } else return object 78 | } 79 | 80 | function arrayToTasks (array, iterator) { 81 | return map(array, function (value, index, list) { 82 | return function (done) { 83 | iterator(done, value, index, list) 84 | } 85 | }) 86 | } 87 | 88 | // ## **Thenjs** 主函数 89 | function Thenjs (start, debug) { 90 | var self = this 91 | var cont 92 | if (start instanceof Thenjs) return start 93 | if (!(self instanceof Thenjs)) return new Thenjs(start, debug) 94 | self._chain = 0 95 | self._success = self._parallel = self._series = null 96 | self._finally = self._error = self._result = self._nextThen = null 97 | if (!arguments.length) return self 98 | 99 | cont = genContinuation(self, debug) 100 | start = toThunk(start) 101 | if (start === void 0) cont() 102 | else if (typeof start === 'function') defer(cont, start, cont) 103 | else cont(null, start) 104 | } 105 | 106 | Thenjs.defer = defer 107 | 108 | Thenjs.parallel = function (tasks, debug) { 109 | return new Thenjs(function (cont) { 110 | carry(cont, parallel, cont, tasks) 111 | }, debug) 112 | } 113 | 114 | Thenjs.series = function (tasks, debug) { 115 | return new Thenjs(function (cont) { 116 | carry(cont, series, cont, tasks) 117 | }, debug) 118 | } 119 | 120 | Thenjs.each = function (array, iterator, debug) { 121 | return new Thenjs(function (cont) { 122 | carry(cont, parallel, cont, arrayToTasks(array, iterator)) 123 | }, debug) 124 | } 125 | 126 | Thenjs.eachSeries = function (array, iterator, debug) { 127 | return new Thenjs(function (cont) { 128 | carry(cont, series, cont, arrayToTasks(array, iterator)) 129 | }, debug) 130 | } 131 | 132 | Thenjs.parallelLimit = function (tasks, limit, debug) { 133 | return new Thenjs(function (cont) { 134 | parallelLimit(cont, tasks, limit) 135 | }, debug) 136 | } 137 | 138 | Thenjs.eachLimit = function (array, iterator, limit, debug) { 139 | return new Thenjs(function (cont) { 140 | parallelLimit(cont, arrayToTasks(array, iterator), limit) 141 | }, debug) 142 | } 143 | 144 | Thenjs.nextTick = function (fn) { 145 | var args = slice(arguments, 1) 146 | nextTick(function () { 147 | fn.apply(null, args) 148 | }) 149 | } 150 | 151 | // 全局 error 监听 152 | Thenjs.onerror = function (error) { 153 | console.error('Thenjs caught error: ', error) 154 | throw error 155 | } 156 | 157 | var proto = Thenjs.prototype 158 | // **Thenjs** 对象上的 **finally** 方法 159 | proto.fin = proto['finally'] = function (finallyHandler) { 160 | return thenFactory(function (cont, self) { 161 | self._finally = wrapTaskHandler(cont, finallyHandler) 162 | }, this) 163 | } 164 | 165 | // **Thenjs** 对象上的 **then** 方法 166 | proto.then = function (successHandler, errorHandler) { 167 | return thenFactory(function (cont, self) { 168 | if (successHandler) self._success = wrapTaskHandler(cont, successHandler) 169 | if (errorHandler) self._error = wrapTaskHandler(cont, errorHandler) 170 | }, this) 171 | } 172 | 173 | // **Thenjs** 对象上的 **fail** 方法 174 | proto.fail = proto['catch'] = function (errorHandler) { 175 | return thenFactory(function (cont, self) { 176 | self._error = wrapTaskHandler(cont, errorHandler) 177 | // 对于链上的 fail 方法,如果无 error ,则穿透该链,将结果输入下一链 178 | self._success = function () { 179 | var args = slice(arguments) 180 | args.unshift(null) 181 | cont.apply(null, args) 182 | } 183 | }, this) 184 | } 185 | 186 | // **Thenjs** 对象上的 **parallel** 方法 187 | proto.parallel = function (tasks) { 188 | return thenFactory(function (cont, self) { 189 | self._parallel = function (_tasks) { 190 | parallel(cont, tasks || _tasks) 191 | } 192 | }, this) 193 | } 194 | 195 | // **Thenjs** 对象上的 **series** 方法 196 | proto.series = function (tasks) { 197 | return thenFactory(function (cont, self) { 198 | self._series = function (_tasks) { 199 | series(cont, tasks || _tasks) 200 | } 201 | }, this) 202 | } 203 | 204 | // **Thenjs** 对象上的 **each** 方法 205 | proto.each = function (array, iterator) { 206 | return thenFactory(function (cont, self) { 207 | self._parallel = function (_array, _iterator) { 208 | // 优先使用定义的参数,如果没有定义参数,则从上一链结果从获取 209 | // `_array`, `_iterator` 来自于上一链的 **cont**,下同 210 | parallel(cont, arrayToTasks(array || _array, iterator || _iterator)) 211 | } 212 | }, this) 213 | } 214 | 215 | // **Thenjs** 对象上的 **eachSeries** 方法 216 | proto.eachSeries = function (array, iterator) { 217 | return thenFactory(function (cont, self) { 218 | self._series = function (_array, _iterator) { 219 | series(cont, arrayToTasks(array || _array, iterator || _iterator)) 220 | } 221 | }, this) 222 | } 223 | 224 | // **Thenjs** 对象上的 **parallelLimit** 方法 225 | proto.parallelLimit = function (tasks, limit) { 226 | return thenFactory(function (cont, self) { 227 | self._parallel = function (_tasks) { 228 | parallelLimit(cont, tasks || _tasks, limit) 229 | } 230 | }, this) 231 | } 232 | 233 | // **Thenjs** 对象上的 **eachLimit** 方法 234 | proto.eachLimit = function (array, iterator, limit) { 235 | return thenFactory(function (cont, self) { 236 | self._series = function (_array, _iterator) { 237 | parallelLimit(cont, arrayToTasks(array || _array, iterator || _iterator), limit) 238 | } 239 | }, this) 240 | } 241 | 242 | // **Thenjs** 对象上的 **toThunk** 方法 243 | proto.toThunk = function () { 244 | var self = this 245 | return function (callback) { 246 | if (self._result) { 247 | callback.apply(null, self._result) 248 | self._result = false 249 | } else if (self._result !== false) { 250 | self._finally = self._error = callback 251 | } 252 | } 253 | } 254 | 255 | // util.inspect() implementation 256 | proto.inspect = function () { 257 | var obj = {} 258 | for (var key in this) { 259 | if (!hasOwnProperty.call(this, key)) continue 260 | obj[key] = key === '_nextThen' ? (this[key] && this[key]._chain) : this[key] 261 | } 262 | return obj 263 | } 264 | 265 | // 核心 **continuation** 方法 266 | // **continuation** 收集任务结果,触发下一个链,它被注入各个 handler 267 | // 其参数采用 **node.js** 的 **callback** 形式:(error, arg1, arg2, ...) 268 | function continuation () { 269 | var self = this 270 | var args = slice(arguments) 271 | 272 | // then链上的结果已经处理,若重复执行 cont 则直接跳过; 273 | if (self._result === false) return 274 | // 第一次进入 continuation,若为 debug 模式则执行,对于同一结果保证 debug 只执行一次; 275 | if (!self._result && self._chain) { 276 | self.debug.apply(self, ['\nChain ' + self._chain + ': '].concat(slice(args))) 277 | } 278 | // 标记已进入 continuation 处理 279 | self._result = false 280 | 281 | carry(function (err) { 282 | if (err === args[0]) continuationError(self, err) 283 | else continuation.call(self._nextThen, err) 284 | }, continuationExec, self, args) 285 | } 286 | 287 | function continuationExec (ctx, args) { 288 | if (args[0] == null) args[0] = null 289 | else { 290 | args = [args[0]] 291 | if (!ctx._finally) throw args[0] 292 | } 293 | if (ctx._finally) return ctx._finally.apply(null, args) 294 | var success = ctx._success || ctx._parallel || ctx._series 295 | if (success) return success.apply(null, slice(args, 1)) 296 | // 对于正确结果,**Thenjs** 链上没有相应 handler 处理,则在 **Thenjs** 链上保存结果,等待下一次处理。 297 | ctx._result = args 298 | } 299 | 300 | function continuationError (ctx, err) { 301 | var _nextThen = ctx 302 | var errorHandler = ctx._error || ctx._finally 303 | 304 | // 获取后链的 error handler 305 | while (!errorHandler && _nextThen._nextThen) { 306 | _nextThen = _nextThen._nextThen 307 | errorHandler = _nextThen._error || _nextThen._finally 308 | } 309 | 310 | if (errorHandler) { 311 | return carry(function (_err) { 312 | // errorHandler 存在则 _nextThen._nextThen 必然存在 313 | continuation.call(_nextThen._nextThen, _err) 314 | }, errorHandler, err) 315 | } 316 | // 如果定义了全局 **onerror**,则用它处理 317 | if (Thenjs.onerror) return Thenjs.onerror(err) 318 | // 对于 error,如果没有任何 handler 处理,则保存到链上最后一个 **Thenjs** 对象,等待下一次处理。 319 | while (_nextThen._nextThen) _nextThen = _nextThen._nextThen 320 | _nextThen._result = [err] 321 | } 322 | 323 | function genContinuation (ctx, debug) { 324 | function cont () { 325 | return continuation.apply(ctx, arguments) 326 | } 327 | // 标记 cont,cont 作为 handler 时不会被注入 cont,见 `wrapTaskHandler` 328 | cont._isCont = true 329 | // 设置并开启 debug 模式 330 | if (debug) { 331 | proto.debug = typeof debug === 'function' ? debug : defaultDebug 332 | ctx._chain = 1 333 | } 334 | return cont 335 | } 336 | 337 | // 注入 cont,执行 fn,并返回新的 **Thenjs** 对象 338 | function thenFactory (fn, ctx, debug) { 339 | var nextThen = new Thenjs() 340 | var cont = genContinuation(nextThen, debug) 341 | 342 | // 注入 cont,初始化 handler 343 | fn(cont, ctx) 344 | if (!ctx) return nextThen 345 | ctx._nextThen = nextThen 346 | if (ctx._chain) nextThen._chain = ctx._chain + 1 347 | // 检查上一链的结果是否处理,未处理则处理,用于续接 **Thenjs** 链 348 | if (ctx._result) { 349 | nextTick(function () { 350 | continuation.apply(ctx, ctx._result) 351 | }) 352 | } 353 | return nextThen 354 | } 355 | 356 | // 封装 handler,`_isCont` 判定 handler 是不是 `cont` ,不是则将 `cont` 注入成第一个参数 357 | function wrapTaskHandler (cont, handler) { 358 | return handler._isCont ? handler : function () { 359 | var args = slice(arguments) 360 | args.unshift(cont) 361 | handler.apply(null, args) 362 | } 363 | } 364 | 365 | // ## **parallel** 函数 366 | // 并行执行一组 `task` 任务,`cont` 处理最后结果 367 | function parallel (cont, tasks) { 368 | if (!isArray(tasks)) return cont(errorify(tasks, 'parallel')) 369 | var pending = tasks.length 370 | var result = [] 371 | 372 | if (pending <= 0) return cont(null, result) 373 | for (var i = 0, len = pending; i < len; i++) tasks[i](genNext(i)) 374 | 375 | function genNext (index) { 376 | function next (error, value) { 377 | if (pending <= 0) return 378 | if (error != null) { 379 | pending = 0 380 | cont(error) 381 | } else { 382 | result[index] = value 383 | return !--pending && cont(null, result) 384 | } 385 | } 386 | next._isCont = true 387 | return next 388 | } 389 | } 390 | 391 | // ## **series** 函数 392 | // 串行执行一组 `array` 任务,`cont` 处理最后结果 393 | function series (cont, tasks) { 394 | if (!isArray(tasks)) return cont(errorify(tasks, 'series')) 395 | var i = 0 396 | var end = tasks.length - 1 397 | var run 398 | var result = [] 399 | var stack = maxTickDepth 400 | 401 | if (end < 0) return cont(null, result) 402 | next._isCont = true 403 | tasks[0](next) 404 | 405 | function next (error, value) { 406 | if (error != null) return cont(error) 407 | result[i] = value 408 | if (++i > end) return cont(null, result) 409 | // 先同步执行,嵌套达到 maxTickDepth 时转成一次异步执行 410 | run = --stack > 0 ? carry : (stack = maxTickDepth, defer) 411 | run(cont, tasks[i], next, result) 412 | } 413 | } 414 | 415 | function parallelLimit (cont, tasks, limit) { 416 | var index = 0 417 | var pending = 0 418 | var len = tasks.length 419 | var queue = [] 420 | var finished = false 421 | 422 | limit = limit >= 1 ? Math.floor(limit) : Number.MAX_VALUE 423 | // eslint-disable-next-line 424 | do { checkNext() } while (index < len && pending < limit) 425 | 426 | function checkNext () { 427 | if (finished) return 428 | if (index >= len) { 429 | finished = true 430 | return parallel(cont, queue) 431 | } 432 | if (pending >= limit) return 433 | pending++ 434 | queue.push(evalTask()) 435 | } 436 | 437 | function evalTask () { 438 | return new Thenjs(tasks[index++]).fin(function (next, err, res) { 439 | if (err != null) { 440 | finished = true 441 | return cont(err) 442 | } 443 | pending-- 444 | checkNext() 445 | next(null, res) 446 | }).toThunk() 447 | } 448 | } 449 | 450 | // 默认的 `debug` 方法 451 | function defaultDebug () { 452 | console.log.apply(console, arguments) 453 | } 454 | 455 | // 参数不合法时生成相应的错误 456 | function errorify (obj, method) { 457 | return new Error('The argument ' + (obj && obj.toString()) + ' in "' + method + '" is not Array!') 458 | } 459 | 460 | Thenjs.NAME = 'Thenjs' 461 | Thenjs.VERSION = '2.1.0' 462 | return Thenjs 463 | })) 464 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global describe, it, Promise, noneFn1, noneFn2 */ 3 | 4 | var assert = require('assert') 5 | var Then = require('../then') 6 | var thunk = require('thunks')() 7 | var x = {} 8 | 9 | Then.onerror = null 10 | 11 | describe('Thenjs', function () { 12 | describe('new Class', function () { 13 | it('Then()', function (done) { 14 | Then() 15 | .then(function (cont, res) { 16 | assert.strictEqual(res, undefined) 17 | done() 18 | }) 19 | }) 20 | 21 | it('Then(null)', function (done) { 22 | Then(null) 23 | .then(function (cont, res) { 24 | assert.strictEqual(res, null) 25 | done() 26 | }) 27 | }) 28 | 29 | it('Then(x)', function (done) { 30 | Then(x) 31 | .then(function (cont, res) { 32 | assert.strictEqual(res, x) 33 | done() 34 | }) 35 | }) 36 | 37 | it('new Then(then)', function (done) { 38 | new Then(Then(x)) 39 | .then(function (cont, res) { 40 | assert.strictEqual(res, x) 41 | done() 42 | }) 43 | }) 44 | 45 | it('Then(thunk)', function (done) { 46 | Then(thunk(x)) 47 | .then(function (cont, res) { 48 | assert.strictEqual(res, x) 49 | done() 50 | }) 51 | }) 52 | 53 | it('Then(resolvedPromise)', function (done) { 54 | if (typeof Promise !== 'function') return done() 55 | Then(Promise.resolve(x)) 56 | .then(function (cont, res) { 57 | assert.strictEqual(res, x) 58 | done() 59 | }) 60 | }) 61 | 62 | it('Then(rejectedPromise)', function (done) { 63 | if (typeof Promise !== 'function') return done() 64 | Then(Promise.reject(x)) 65 | .then(function (cont, res) { 66 | assert.strictEqual('It will not run', true) 67 | }, function (cont, err) { 68 | assert.strictEqual(err, x) 69 | done() 70 | }) 71 | }) 72 | 73 | it('Then(resolvedThen)', function (done) { 74 | Then(function (cont) { 75 | cont(null, 1, 2, 3) 76 | }) 77 | .then(function (cont, res) { 78 | assert.strictEqual(res, 1) 79 | assert.strictEqual(arguments[2], 2) 80 | assert.strictEqual(arguments[3], 3) 81 | done() 82 | }) 83 | }) 84 | 85 | it('Then(rejectedThen)', function (done) { 86 | Then(function (cont) { 87 | cont(x, 1, 2, 3) 88 | }) 89 | .then(function (cont, res) { 90 | assert.strictEqual('It will not run', true) 91 | }, function (cont, err) { 92 | assert.strictEqual(err, x) 93 | done() 94 | }) 95 | }) 96 | 97 | it('Then(fn) throw error', function (done) { 98 | Then(function (cont) { 99 | noneFn1() 100 | }) 101 | .then(function (cont, res) { 102 | assert.strictEqual('It will not run', true) 103 | }, function (cont, err) { 104 | assert.strictEqual(err instanceof Error, true) 105 | done() 106 | }) 107 | }) 108 | }) 109 | 110 | describe('prototype', function () { 111 | it('.toThunk', function (done) { 112 | Then(x).toThunk()(function (err, res) { 113 | assert.strictEqual(err, null) 114 | assert.strictEqual(res, x) 115 | 116 | Then(function (cont) { 117 | noneFn1() 118 | }).toThunk()(function (err, res) { 119 | assert.strictEqual(err instanceof Error, true) 120 | done() 121 | }) 122 | }) 123 | }) 124 | 125 | it('.then and debug options', function (done) { 126 | var chains = [] 127 | 128 | Then(x, function () { 129 | chains.push(this._chain) 130 | }) 131 | .then(function (cont, res) { 132 | assert.strictEqual(res, x) 133 | cont(new Error('error 1')) 134 | }, function (cont, err) { 135 | assert.strictEqual('It will not run', true) 136 | }) 137 | .then(function (cont, res) { 138 | assert.strictEqual('It will not run', true) 139 | }, function (cont, err) { 140 | assert.strictEqual(err instanceof Error, true) 141 | assert.strictEqual(err.message, 'error 1') 142 | cont(new Error('error 2')) 143 | }) 144 | .then(null, function (cont, err) { 145 | assert.strictEqual(err instanceof Error, true) 146 | assert.strictEqual(err.message, 'error 2') 147 | cont(null, x) 148 | }) 149 | .then(function (cont, res) { 150 | assert.strictEqual(res, x) 151 | noneFn1() 152 | }, function (cont, err) { 153 | assert.strictEqual('It will not run', true) 154 | }) 155 | .then(function (cont, res) { 156 | assert.strictEqual('It will not run', true) 157 | }, function (cont, err) { 158 | assert.strictEqual(err instanceof Error, true) 159 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 160 | noneFn2() 161 | }) 162 | .then(function (cont, res) { 163 | assert.strictEqual('It will not run', true) 164 | }, function (cont, err) { 165 | assert.strictEqual(err instanceof Error, true) 166 | assert.strictEqual(err.message.indexOf('noneFn2') >= 0, true) 167 | cont(null, x) 168 | }) 169 | .then(function (cont, res) { 170 | assert.strictEqual(res, x) 171 | assert.deepEqual(chains, [1, 2, 3, 4, 5, 6, 7]) 172 | cont() 173 | }).toThunk()(done) 174 | }) 175 | 176 | it('.fin and debug options', function (done) { 177 | var chains = [] 178 | 179 | Then(x, function () { 180 | chains.push(this._chain) 181 | }) 182 | .fin(function (cont, err, res) { 183 | assert.strictEqual(err, null) 184 | assert.strictEqual(res, x) 185 | cont(new Error('error 1'), x) 186 | }) 187 | .fin(function (cont, err, res) { 188 | assert.strictEqual(err instanceof Error, true) 189 | assert.strictEqual(err.message, 'error 1') 190 | assert.strictEqual(res, undefined) 191 | noneFn1() 192 | }) 193 | .then(function (cont) { 194 | assert.strictEqual('It will not run', true) 195 | }) 196 | .then(function (cont) { 197 | assert.strictEqual('It will not run', true) 198 | }, function (cont, err) { 199 | assert.strictEqual(err instanceof Error, true) 200 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 201 | noneFn2() 202 | }) 203 | .then(function (cont) { 204 | assert.strictEqual('It will not run', true) 205 | }) 206 | .fin(function (cont, err, res) { 207 | assert.strictEqual(err instanceof Error, true) 208 | assert.strictEqual(err.message.indexOf('noneFn2') >= 0, true) 209 | cont(null, x) 210 | }) 211 | .then(function (cont, res) { 212 | assert.strictEqual(res, x) 213 | Then(x) 214 | .fin(function (cont2, err, res) { 215 | assert.strictEqual(err, null) 216 | assert.strictEqual(res, x) 217 | cont2(new Error('error 2')) 218 | }) 219 | .fin(cont) 220 | }) 221 | .then(function (cont) { 222 | assert.strictEqual('It will not run', true) 223 | }, function (cont, err) { 224 | assert.strictEqual(err instanceof Error, true) 225 | assert.strictEqual(err.message, 'error 2') 226 | Then() 227 | .fin(function (cont2) { 228 | cont2(null, 1, 2, 3) 229 | }) 230 | .fin(cont) 231 | }) 232 | .then(function (cont) { 233 | assert.strictEqual(arguments[1], 1) 234 | assert.strictEqual(arguments[2], 2) 235 | assert.strictEqual(arguments[3], 3) 236 | assert.deepEqual(chains, [1, 2, 3, 5, 7, 8, 9]) 237 | cont() 238 | }).toThunk()(done) 239 | }) 240 | 241 | it('.fail and debug options', function (done) { 242 | var chains = [] 243 | 244 | Then(x, function () { 245 | chains.push(this._chain) 246 | }) 247 | .fail(function (cont, err) { 248 | assert.strictEqual('It will not run', true) 249 | }) 250 | .then(function (cont, res) { 251 | assert.strictEqual(res, x) 252 | cont(new Error('error 1')) 253 | }) 254 | .then(function (cont, res) { 255 | assert.strictEqual('It will not run', true) 256 | }) 257 | .then(function (cont, res) { 258 | assert.strictEqual('It will not run', true) 259 | }) 260 | .fail(function (cont, err) { 261 | assert.strictEqual(err instanceof Error, true) 262 | assert.strictEqual(err.message, 'error 1') 263 | noneFn1() 264 | }) 265 | .fail(function (cont, err) { 266 | assert.strictEqual(err instanceof Error, true) 267 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 268 | noneFn2() 269 | }) 270 | .fin(function (cont, err, res) { 271 | assert.strictEqual(err instanceof Error, true) 272 | assert.strictEqual(err.message.indexOf('noneFn2') >= 0, true) 273 | Then() 274 | .then(function (cont2) { 275 | noneFn1() 276 | }) 277 | .fail(cont) 278 | }) 279 | .then(function (cont, res) { 280 | assert.strictEqual('It will not run', true) 281 | }) 282 | .fail(function (cont, err) { 283 | assert.strictEqual(err instanceof Error, true) 284 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 285 | assert.deepEqual(chains, [1, 2, 3, 6, 7, 8]) 286 | cont() 287 | }).toThunk()(done) 288 | }) 289 | 290 | it('.parallel', function (done) { 291 | var pending = [] 292 | Then() 293 | .parallel([ 294 | function (cont) { 295 | pending.push(1) 296 | assert.strictEqual(pending.length, 1) 297 | cont(null, 1) 298 | }, 299 | function (cont) { 300 | assert.strictEqual(pending.length, 1) 301 | setTimeout(function () { 302 | pending.push(2) 303 | assert.strictEqual(pending.length, 3) 304 | cont(null, 2, 3) 305 | }) 306 | }, 307 | function (cont) { 308 | pending.push(3) 309 | assert.strictEqual(pending.length, 2) 310 | cont(null, 4) 311 | } 312 | ]) 313 | .then(function (cont, res) { 314 | assert.deepEqual(pending, [1, 3, 2]) 315 | assert.deepEqual(res, [1, 2, 4]) 316 | cont(null, [ 317 | function (cont) { 318 | setTimeout(function () { 319 | cont(null, x) 320 | }) 321 | }, 322 | function (cont) { 323 | cont(null, null) 324 | } 325 | ]) 326 | }) 327 | .parallel(null) 328 | .then(function (cont, res) { 329 | assert.deepEqual(res, [x, null]) 330 | cont(null, x) 331 | }) 332 | .parallel([ 333 | function (cont) { 334 | cont(null, x) 335 | }, 336 | function (cont) { 337 | noneFn1() 338 | cont(null, x) 339 | } 340 | ]) 341 | .fin(function (cont, err, res) { 342 | assert.strictEqual(err instanceof Error, true) 343 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 344 | assert.strictEqual(res, void 0) 345 | cont() 346 | }) 347 | .toThunk()(done) 348 | }) 349 | 350 | it('.series', function (done) { 351 | var pending = [] 352 | Then() 353 | .series([ 354 | function (cont) { 355 | pending.push(1) 356 | assert.strictEqual(pending.length, 1) 357 | cont(null, 1) 358 | }, 359 | function (cont) { 360 | assert.strictEqual(pending.length, 1) 361 | setTimeout(function () { 362 | pending.push(2) 363 | assert.strictEqual(pending.length, 2) 364 | cont(null, 2, 3) 365 | }) 366 | }, 367 | function (cont) { 368 | pending.push(3) 369 | assert.strictEqual(pending.length, 3) 370 | cont(null, 4) 371 | } 372 | ]) 373 | .then(function (cont, res) { 374 | assert.deepEqual(pending, [1, 2, 3]) 375 | assert.deepEqual(res, [1, 2, 4]) 376 | cont(null, [ 377 | function (cont) { 378 | setTimeout(function () { 379 | cont(null, x) 380 | }) 381 | }, 382 | function (cont) { 383 | cont(null, null) 384 | } 385 | ]) 386 | }) 387 | .series(null) 388 | .then(function (cont, res) { 389 | assert.deepEqual(res, [x, null]) 390 | cont(null, x) 391 | }) 392 | .series([ 393 | function (cont) { 394 | cont(null, x) 395 | }, 396 | function (cont) { 397 | noneFn1() 398 | cont(null, x) 399 | } 400 | ]) 401 | .fin(function (cont, err, res) { 402 | assert.strictEqual(err instanceof Error, true) 403 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 404 | assert.strictEqual(res, void 0) 405 | cont() 406 | }).toThunk()(done) 407 | }) 408 | 409 | it('.series-result', function (done) { 410 | var pending = [] 411 | Then() 412 | .series([ 413 | function (cont, result) { 414 | pending.push(1) 415 | assert.strictEqual(pending.length, 1) 416 | assert.equal('undefined', typeof result) 417 | cont(null, 1) 418 | }, 419 | function (cont, result) { 420 | assert.strictEqual(pending.length, 1) 421 | setTimeout(function () { 422 | pending.push(2) 423 | assert.strictEqual(pending.length, 2) 424 | assert.equal(1, result[0]) 425 | cont(null, 2, 3) 426 | }) 427 | }, 428 | function (cont, result) { 429 | pending.push(3) 430 | assert.strictEqual(pending.length, 3) 431 | assert.equal(1, result[0]) 432 | assert.equal(2, result[1]) 433 | cont(null, 4) 434 | } 435 | ]) 436 | .then(function (cont, res) { 437 | assert.deepEqual(pending, [1, 2, 3]) 438 | assert.deepEqual(res, [1, 2, 4]) 439 | cont(null, [ 440 | function (cont) { 441 | setTimeout(function () { 442 | cont(null, x) 443 | }) 444 | }, 445 | function (cont, result) { 446 | assert.deepEqual(x, result[0]) 447 | cont(null, null) 448 | } 449 | ]) 450 | }) 451 | .series(null) 452 | .then(function (cont, res) { 453 | assert.deepEqual(res, [x, null]) 454 | cont(null, x) 455 | }) 456 | .series([ 457 | function (cont) { 458 | cont(null, x) 459 | }, 460 | function (cont, result) { 461 | assert.deepEqual(x, result[0]) 462 | noneFn1() 463 | cont(null, x) 464 | } 465 | ]) 466 | .fin(function (cont, err, res) { 467 | assert.strictEqual(err instanceof Error, true) 468 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 469 | assert.strictEqual(res, void 0) 470 | cont() 471 | }).toThunk()(done) 472 | }) 473 | 474 | it('.each', function (done) { 475 | var pending = [] 476 | Then() 477 | .each([0, 1, 2], function (cont, value, index, list) { 478 | assert.strictEqual(value, index) 479 | assert.strictEqual(list.length, 3) 480 | pending.push(index) 481 | setTimeout(function () { 482 | assert.strictEqual(pending.length, 3) 483 | cont(null, value) 484 | }) 485 | }) 486 | .then(function (cont, res) { 487 | assert.deepEqual(pending, [0, 1, 2]) 488 | assert.deepEqual(res, pending) 489 | cont(null, [4, 5, 6]) 490 | }) 491 | .each(null, function (cont, value) { 492 | cont(null, value) 493 | }) 494 | .then(function (cont, res) { 495 | assert.deepEqual(res, [4, 5, 6]) 496 | cont() 497 | }) 498 | .each([1, 2, 3], function (cont, value) { 499 | noneFn1() 500 | }) 501 | .fin(function (cont, err, res) { 502 | assert.strictEqual(err instanceof Error, true) 503 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 504 | assert.strictEqual(res, void 0) 505 | cont() 506 | }).toThunk()(done) 507 | }) 508 | 509 | it('.eachSeries', function (done) { 510 | var pending = [] 511 | Then() 512 | .eachSeries([0, 1, 2], function (cont, value, index, list) { 513 | assert.strictEqual(value, index) 514 | assert.strictEqual(list.length, 3) 515 | pending.push(index) 516 | setTimeout(function () { 517 | assert.strictEqual(pending.length, index + 1) 518 | cont(null, value) 519 | }) 520 | }) 521 | .then(function (cont, res) { 522 | assert.deepEqual(pending, [0, 1, 2]) 523 | assert.deepEqual(res, pending) 524 | cont(null, [4, 5, 6]) 525 | }) 526 | .eachSeries(null, function (cont, value) { 527 | cont(null, value) 528 | }) 529 | .then(function (cont, res) { 530 | assert.deepEqual(res, [4, 5, 6]) 531 | cont() 532 | }) 533 | .eachSeries([1, 2, 3], function (cont, value) { 534 | noneFn1() 535 | }) 536 | .fin(function (cont, err, res) { 537 | assert.strictEqual(err instanceof Error, true) 538 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 539 | assert.strictEqual(res, void 0) 540 | cont() 541 | }).toThunk()(done) 542 | }) 543 | 544 | it('.parallelLimit', function (done) { 545 | var i = 0 546 | var limit = 50 547 | var current = 0 548 | var pending = [] 549 | var tasks = [] 550 | 551 | while (i < 100) tasks.push(genTask(i++)) 552 | 553 | function genTask (index) { 554 | return function (callback) { 555 | current++ 556 | pending.push(index) 557 | assert.strictEqual(current <= limit, true) 558 | setTimeout(function () { 559 | current-- 560 | callback(null, index) 561 | }, 20) 562 | } 563 | } 564 | 565 | Then() 566 | .parallelLimit(tasks, 50) 567 | .then(function (cont, res) { 568 | assert.deepEqual(res, pending) 569 | limit = 10 570 | current = 0 571 | pending = [] 572 | cont(null, tasks) 573 | }) 574 | .parallelLimit(null, 10) 575 | .then(function (cont, res) { 576 | assert.deepEqual(res, pending) 577 | cont() 578 | }).toThunk()(done) 579 | }) 580 | 581 | it('.eachLimit', function (done) { 582 | var i = 0 583 | var limit = 50 584 | var current = 0 585 | var pending = [] 586 | var array = [] 587 | 588 | while (i < 100) array.push(i++) 589 | 590 | function iterator (callback, value, index, list) { 591 | current++ 592 | pending.push(index) 593 | assert.strictEqual(value, index) 594 | assert.strictEqual(current <= limit, true) 595 | setTimeout(function () { 596 | current-- 597 | callback(null, index) 598 | }, 20) 599 | } 600 | 601 | Then() 602 | .eachLimit(array, iterator, 50) 603 | .then(function (cont, res) { 604 | assert.deepEqual(res, pending) 605 | limit = 10 606 | current = 0 607 | pending = [] 608 | cont(null, array) 609 | }) 610 | .eachLimit(null, iterator, 10) 611 | .then(function (cont, res) { 612 | assert.deepEqual(res, pending) 613 | cont() 614 | }).toThunk()(done) 615 | }) 616 | }) 617 | 618 | describe('Class method', function () { 619 | it('Then.parallel', function (done) { 620 | var pending = [] 621 | Then 622 | .parallel([ 623 | function (cont) { 624 | pending.push(1) 625 | assert.strictEqual(pending.length, 1) 626 | cont(null, 1) 627 | }, 628 | function (cont) { 629 | assert.strictEqual(pending.length, 1) 630 | setTimeout(function () { 631 | pending.push(2) 632 | assert.strictEqual(pending.length, 3) 633 | cont(null, 2, 3) 634 | }) 635 | }, 636 | function (cont) { 637 | pending.push(3) 638 | assert.strictEqual(pending.length, 2) 639 | cont(null, 4) 640 | } 641 | ]) 642 | .then(function (cont, res) { 643 | assert.deepEqual(pending, [1, 3, 2]) 644 | assert.deepEqual(res, [1, 2, 4]) 645 | cont(null, [ 646 | function (cont) { 647 | setTimeout(function () { 648 | cont(null, x) 649 | }) 650 | }, 651 | function (cont) { 652 | cont(null, null) 653 | } 654 | ]) 655 | }) 656 | .parallel(null) 657 | .then(function (cont, res) { 658 | assert.deepEqual(res, [x, null]) 659 | cont(null, x) 660 | }) 661 | .parallel([ 662 | function (cont) { 663 | cont(null, x) 664 | }, 665 | function (cont) { 666 | noneFn1() 667 | cont(null, x) 668 | } 669 | ]) 670 | .fin(function (cont, err, res) { 671 | assert.strictEqual(err instanceof Error, true) 672 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 673 | assert.strictEqual(res, void 0) 674 | cont() 675 | }) 676 | .toThunk()(done) 677 | }) 678 | 679 | it('Then.series', function (done) { 680 | var pending = [] 681 | Then 682 | .series([ 683 | function (cont) { 684 | pending.push(1) 685 | assert.strictEqual(pending.length, 1) 686 | cont(null, 1) 687 | }, 688 | function (cont) { 689 | assert.strictEqual(pending.length, 1) 690 | setTimeout(function () { 691 | pending.push(2) 692 | assert.strictEqual(pending.length, 2) 693 | cont(null, 2, 3) 694 | }) 695 | }, 696 | function (cont) { 697 | pending.push(3) 698 | assert.strictEqual(pending.length, 3) 699 | cont(null, 4) 700 | } 701 | ]) 702 | .then(function (cont, res) { 703 | assert.deepEqual(pending, [1, 2, 3]) 704 | assert.deepEqual(res, [1, 2, 4]) 705 | cont(null, [ 706 | function (cont) { 707 | setTimeout(function () { 708 | cont(null, x) 709 | }) 710 | }, 711 | function (cont) { 712 | cont(null, null) 713 | } 714 | ]) 715 | }) 716 | .series(null) 717 | .then(function (cont, res) { 718 | assert.deepEqual(res, [x, null]) 719 | cont(null, x) 720 | }) 721 | .series([ 722 | function (cont) { 723 | cont(null, x) 724 | }, 725 | function (cont) { 726 | noneFn1() 727 | cont(null, x) 728 | } 729 | ]) 730 | .fin(function (cont, err, res) { 731 | assert.strictEqual(err instanceof Error, true) 732 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 733 | assert.strictEqual(res, void 0) 734 | cont() 735 | }).toThunk()(done) 736 | }) 737 | 738 | it('Then.each', function (done) { 739 | var pending = [] 740 | Then 741 | .each([0, 1, 2], function (cont, value, index, list) { 742 | assert.strictEqual(value, index) 743 | assert.strictEqual(list.length, 3) 744 | pending.push(index) 745 | setTimeout(function () { 746 | assert.strictEqual(pending.length, 3) 747 | cont(null, value) 748 | }) 749 | }) 750 | .then(function (cont, res) { 751 | assert.deepEqual(pending, [0, 1, 2]) 752 | assert.deepEqual(res, pending) 753 | cont(null, [4, 5, 6]) 754 | }) 755 | .each(null, function (cont, value) { 756 | cont(null, value) 757 | }) 758 | .then(function (cont, res) { 759 | assert.deepEqual(res, [4, 5, 6]) 760 | cont() 761 | }) 762 | .each([1, 2, 3], function (cont, value) { 763 | noneFn1() 764 | }) 765 | .fin(function (cont, err, res) { 766 | assert.strictEqual(err instanceof Error, true) 767 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 768 | assert.strictEqual(res, void 0) 769 | cont() 770 | }).toThunk()(done) 771 | }) 772 | 773 | it('Then.eachSeries', function (done) { 774 | var pending = [] 775 | Then 776 | .eachSeries([0, 1, 2], function (cont, value, index, list) { 777 | assert.strictEqual(value, index) 778 | assert.strictEqual(list.length, 3) 779 | pending.push(index) 780 | setTimeout(function () { 781 | assert.strictEqual(pending.length, index + 1) 782 | cont(null, value) 783 | }) 784 | }) 785 | .then(function (cont, res) { 786 | assert.deepEqual(pending, [0, 1, 2]) 787 | assert.deepEqual(res, pending) 788 | cont(null, [4, 5, 6]) 789 | }) 790 | .eachSeries(null, function (cont, value) { 791 | cont(null, value) 792 | }) 793 | .then(function (cont, res) { 794 | assert.deepEqual(res, [4, 5, 6]) 795 | cont() 796 | }) 797 | .eachSeries([1, 2, 3], function (cont, value) { 798 | noneFn1() 799 | }) 800 | .fin(function (cont, err, res) { 801 | assert.strictEqual(err instanceof Error, true) 802 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 803 | assert.strictEqual(res, void 0) 804 | cont() 805 | }).toThunk()(done) 806 | }) 807 | 808 | it('Then.parallelLimit', function (done) { 809 | var i = 0 810 | var limit = 50 811 | var current = 0 812 | var pending = [] 813 | var tasks = [] 814 | 815 | while (i < 100) tasks.push(genTask(i++)) 816 | 817 | function genTask (index) { 818 | return function (callback) { 819 | current++ 820 | pending.push(index) 821 | assert.strictEqual(current <= limit, true) 822 | setTimeout(function () { 823 | current-- 824 | callback(null, index) 825 | }, 20) 826 | } 827 | } 828 | 829 | Then 830 | .parallelLimit(tasks, 50) 831 | .then(function (cont, res) { 832 | assert.deepEqual(res, pending) 833 | limit = 10 834 | current = 0 835 | pending = [] 836 | cont(null, tasks) 837 | }) 838 | .parallelLimit(null, 10) 839 | .then(function (cont, res) { 840 | assert.deepEqual(res, pending) 841 | cont() 842 | }).toThunk()(done) 843 | }) 844 | 845 | it('Then.eachLimit', function (done) { 846 | var i = 0 847 | var limit = 50 848 | var current = 0 849 | var pending = [] 850 | var array = [] 851 | 852 | while (i < 100) array.push(i++) 853 | 854 | function iterator (callback, value, index, list) { 855 | current++ 856 | pending.push(index) 857 | assert.strictEqual(value, index) 858 | assert.strictEqual(current <= limit, true) 859 | setTimeout(function () { 860 | current-- 861 | callback(null, index) 862 | }, 20) 863 | } 864 | 865 | Then 866 | .eachLimit(array, iterator, 50) 867 | .then(function (cont, res) { 868 | assert.deepEqual(res, pending) 869 | limit = 10 870 | current = 0 871 | pending = [] 872 | cont(null, array) 873 | }) 874 | .eachLimit(null, iterator, 10) 875 | .then(function (cont, res) { 876 | assert.deepEqual(res, pending) 877 | cont() 878 | }).toThunk()(done) 879 | }) 880 | 881 | it('Then.onerror', function (done) { 882 | Then.onerror = function (err) { 883 | assert.strictEqual(err instanceof Error, true) 884 | assert.strictEqual(err.message.indexOf('noneFn2') >= 0, true) 885 | done() 886 | } 887 | 888 | Then(function () { 889 | noneFn1() 890 | }) 891 | .then(function (cont, res) { 892 | assert.strictEqual('It will not run', true) 893 | }) 894 | .fin(function (cont, err) { 895 | assert.strictEqual(err instanceof Error, true) 896 | assert.strictEqual(err.message.indexOf('noneFn1') >= 0, true) 897 | cont(null, x) 898 | }) 899 | .then(function (cont, res) { 900 | assert.strictEqual(res, x) 901 | noneFn2() 902 | }) 903 | .then(function (cont, res) { 904 | assert.strictEqual('It will not run', true) 905 | }) 906 | }) 907 | }) 908 | }) 909 | --------------------------------------------------------------------------------