├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── History.md ├── Makefile ├── Readme.md ├── benchmark └── base.js ├── example.js ├── lib ├── event.js ├── genify.js ├── index.js └── thunkify.js ├── package.json └── test ├── ctx.js ├── event.js ├── genify.js ├── index.js ├── methods.js ├── object.js └── support ├── cal.js └── read.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | coverage.html 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | 15 | node_modules 16 | npm-debug.log -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .tmp/ 4 | .git/ 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 14 | "indent" : false, // {int} Number of spaces to use for indentation 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : false, // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : false, // true: Require all defined variables be used 28 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 29 | "trailing" : false, // true: Prohibit trailing whitespaces 30 | "maxparams" : false, // {int} Max number of formal params allowed per function 31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 32 | "maxstatements" : false, // {int} Max number statements per function 33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 34 | "maxlen" : false, // {int} Max number of characters per line 35 | 36 | // Relaxing 37 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 38 | "boss" : true, // true: Tolerate assignments where comparisons would be expected 39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 40 | "eqnull" : false, // true: Tolerate use of `== null` 41 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 42 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 43 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 44 | // (ex: `for each`, multiple try/catch, function expression…) 45 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 46 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 47 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 48 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 49 | "iterator" : false, // true: Tolerate using the `__iterator__` property 50 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 51 | "laxbreak" : true, // true: Tolerate possibly unsafe line breakings 52 | "laxcomma" : false, // true: Tolerate comma-first style coding 53 | "loopfunc" : false, // true: Tolerate functions being defined in loops 54 | "multistr" : true, // true: Tolerate multi-line strings 55 | "proto" : false, // true: Tolerate using the `__proto__` property 56 | "scripturl" : false, // true: Tolerate script-targeted URLs 57 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 58 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 59 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 60 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 61 | "validthis" : true, // true: Tolerate using this in a non-constructor function 62 | 63 | // Environments 64 | "browser" : true, // Web Browser (window, document, etc) 65 | "couch" : false, // CouchDB 66 | "devel" : true, // Development/debugging (alert, confirm, etc) 67 | "dojo" : false, // Dojo Toolkit 68 | "jquery" : false, // jQuery 69 | "mootools" : false, // MooTools 70 | "node" : true, // Node.js 71 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 72 | "prototypejs" : false, // Prototype and Scriptaculous 73 | "rhino" : false, // Rhino 74 | "worker" : false, // Web Workers 75 | "wsh" : false, // Windows Scripting Host 76 | "yui" : false, // Yahoo User Interface 77 | "noyield" : true, // allow generators without a yield 78 | 79 | // Legacy 80 | "nomen" : false, // true: Prohibit dangling `_` in variables 81 | "onevar" : false, // true: Allow only one `var` statement per function 82 | "passfail" : false, // true: Stop on first error 83 | "white" : false, // true: Check against strict whitespace and indentation rules 84 | 85 | // Custom Globals 86 | "globals" : { // additional predefined global variables 87 | // mocha 88 | "describe": true, 89 | "it": true, 90 | "before": true, 91 | "afterEach": true, 92 | "beforeEach": true, 93 | "after": true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.11' 4 | script: make test 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.4 / 2014-11-07 3 | ================== 4 | 5 | * fix thunkify undefined error 6 | 7 | 1.0.3 / 2014-10-30 8 | ================== 9 | 10 | * support 0.10 11 | 12 | 1.0.2 / 2014-07-31 13 | ================== 14 | 15 | * is generator function filter 16 | 17 | 1.0.1 / 2014-07-10 18 | ================== 19 | 20 | * improve performance 21 | 22 | 1.0.0 / 2014-07-10 23 | ================== 24 | 25 | * Merge pull request #1 from node-modules/genify 26 | * yield* asyncGen(a, 1) slow 1.5x than yield asyncThunk(a, 1) 27 | * genify return GeneratorFunction 28 | 29 | 0.1.2 / 2014-05-25 30 | ================== 31 | 32 | * add thunkify(obj, method) 33 | * fix travis link 34 | 35 | 0.1.1 / 2014-03-18 36 | ================== 37 | 38 | * update thunkify.event, fix unremove listener error 39 | 40 | 0.1.0 / 2014-03-17 41 | ================== 42 | 43 | * fix event 44 | 45 | 0.0.5 / 2014-03-06 46 | ================== 47 | 48 | * check repeat thunkify 49 | 50 | 0.0.4 / 2014-03-05 51 | ================== 52 | 53 | * add event in readme 54 | * add test for event 55 | * add event support 56 | 57 | 0.0.3 / 2014-02-28 58 | ================== 59 | 60 | * support methods 61 | * fix test in 0.10 62 | 63 | 0.0.2 / 2014-02-28 64 | ================== 65 | 66 | * add example 67 | * add ctx, remove thunkify dep 68 | * fix test 69 | 70 | 0.0.1 / 2014-02-17 71 | ================== 72 | 73 | * complete wrap and test 74 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.js 2 | REPORTER = spec 3 | TIMEOUT = 5000 4 | MOCHA_OPTS = 5 | NPM_INSTALL = npm install --registry=https://registry.npm.taobao.org 6 | install: 7 | @$(NPM_INSTALL) 8 | 9 | test: install 10 | @NODE_ENV=test ./node_modules/mocha/bin/mocha \ 11 | --harmony \ 12 | --reporter $(REPORTER) \ 13 | --timeout $(TIMEOUT) \ 14 | --require co-mocha \ 15 | $(MOCHA_OPTS) \ 16 | $(TESTS) 17 | 18 | test-all: test 19 | 20 | .PHONY: install test 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | thunkify-wrap 3 | =========== 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![build status][travis-image]][travis-url] 7 | [![Test coverage][coveralls-image]][coveralls-url] 8 | [![Gittip][gittip-image]][gittip-url] 9 | [![David deps][david-image]][david-url] 10 | [![node version][node-image]][node-url] 11 | [![npm download][download-image]][download-url] 12 | 13 | [npm-image]: https://img.shields.io/npm/v/thunkify-wrap.svg?style=flat-square 14 | [npm-url]: https://npmjs.org/package/thunkify-wrap 15 | [travis-image]: https://img.shields.io/travis/node-modules/thunkify-wrap.svg?style=flat-square 16 | [travis-url]: https://travis-ci.org/node-modules/thunkify-wrap 17 | [coveralls-image]: https://img.shields.io/coveralls/node-modules/thunkify-wrap.svg?style=flat-square 18 | [coveralls-url]: https://coveralls.io/r/node-modules/thunkify-wrap?branch=master 19 | [gittip-image]: https://img.shields.io/gittip/dead-horse.svg?style=flat-square 20 | [gittip-url]: https://www.gittip.com/dead-horse/ 21 | [david-image]: https://img.shields.io/david/node-modules/thunkify-wrap.svg?style=flat-square 22 | [david-url]: https://david-dm.org/node-modules/thunkify-wrap 23 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square 24 | [node-url]: http://nodejs.org/download/ 25 | [download-image]: https://img.shields.io/npm/dm/thunkify-wrap.svg?style=flat-square 26 | [download-url]: https://npmjs.org/package/thunkify-wrap 27 | 28 | Turn each node function in an object return a thunk. 29 | Turn a regular node function into one which returns a thunk, 30 | useful for generator-based flow control such as [co](https://github.com/visionmedia/co). 31 | 32 | ## Installation 33 | 34 | ```sh 35 | npm install thunkify-wrap --save 36 | ``` 37 | 38 | ## Example 39 | 40 | ```js 41 | // the same as thunkify 42 | var thunkify = require('thunkify-wrap'); 43 | var fs = require('fs'); 44 | 45 | fs.readFile = thunkify(fs.readFile); 46 | 47 | fs.readFile('package.json', 'utf8')(function(err, str){ 48 | 49 | }); 50 | 51 | // thunkify an object 52 | var user = { 53 | add: function () {}, 54 | show: function () {}, 55 | list: function () {} 56 | } 57 | 58 | module.exports = thunkify(user); 59 | // module.exports = thunkify(user, ['add', 'show']); 60 | // module.exports = thunkify(user, 'add'); 61 | ``` 62 | 63 | ## genify 64 | 65 | Wrap every function return a `GeneratorFunction`, 66 | that will be easy to write codes in only one way: `yield* fn()`. 67 | 68 | ```js 69 | var genify = require('thunkify-wrap').genify; 70 | var fs = require('fs'); 71 | 72 | fs.readFile = genify(fs.readFile); 73 | 74 | var content = yield* fs.readFile(__filename, 'utf8'); 75 | ``` 76 | 77 | ## event support 78 | 79 | you can pass an event object, give end event name list, wrap event to thunk like this 80 | 81 | ``` 82 | var e = new EventEmitter(); 83 | var end = thunkify.event(e, 'finish'); 84 | 85 | yield end(); 86 | or 87 | yield.end(['close', 'end']); // will cover `finish` event 88 | ``` 89 | 90 | when specified events emitted, this generator will go on. see more in the source code. 91 | 92 | ## ctx 93 | 94 | also you can pass `ctx` as contenxt into thunkify, and `thunkify(object)` will use object as the context by default. 95 | 96 | ```js 97 | var thunkify = require('thunkify-wrap'); 98 | var Cal = function (a, b) { 99 | this.a = a; 100 | this.b = b; 101 | }; 102 | 103 | Cal.prototype.plus = function(callback) { 104 | var self = this; 105 | setTimeout(function () { 106 | callback(null, self.a + self.b); 107 | }, 5); 108 | }; 109 | 110 | Cal.prototype.minus = function (callback) { 111 | var self = this; 112 | setTimeout(function () { 113 | callback(null, self.a - self.b); 114 | }, 5); 115 | }; 116 | 117 | module.exports = Cal; 118 | 119 | exports.create1 = function (a, b) { 120 | return thunkify(new Cal(a, b)); 121 | }; 122 | 123 | // or 124 | exports.create2 = function (a, b) { 125 | var cal = new Cal(a, b); 126 | cal.plus = thunkify(cal.plus, cal); 127 | cal.minus = thunkify(cal.minus, cal); 128 | }; 129 | ``` 130 | 131 | ### methods 132 | 133 | by pass `methods` list, support only thunkify a part of methods in an object. 134 | 135 | ``` 136 | exports.create3 = function (a, b) { 137 | var cal = new Cal(a, b); 138 | thunkify(cal, cal, ['plus']); 139 | // or 140 | thunkify(cal, ['plus']); 141 | }; 142 | ``` 143 | 144 | # License 145 | 146 | MIT 147 | -------------------------------------------------------------------------------- /benchmark/base.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * node-thunkify-wrap - benchmark/base.js 3 | * 4 | * Copyright(c) fengmk2 and other contributors. 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var co = require('co'); 18 | var thunkify = require('../'); 19 | 20 | function async(foo, bar, callback) { 21 | setImmediate(function () { 22 | callback(null, {foo: foo, bar: bar}); 23 | }); 24 | } 25 | 26 | var n = 1000000; 27 | 28 | var asyncThunk = thunkify(async); 29 | var asyncGen = thunkify.genify(async); 30 | 31 | console.log('\n thunkify benchmark\n node version: %s, date: %s\n Starting...\n', 32 | process.version, Date()); 33 | 34 | co(function* () { 35 | var start = Date.now(); 36 | for (var i = 0; i < n; i++) { 37 | yield asyncThunk('a', 1); 38 | } 39 | var use = Date.now() - start; 40 | console.log(" yield asyncThunk('a', 1) %d times, use: %sms, qps: %s", n, use, n / use * 1000); 41 | 42 | var start = Date.now(); 43 | for (var i = 0; i < n; i++) { 44 | yield asyncGen('a', 1); 45 | } 46 | var use = Date.now() - start; 47 | console.log(" yield asyncGen('a', 1) %d times, use: %sms, qps: %s", n, use, n / use * 1000); 48 | 49 | var start = Date.now(); 50 | for (var i = 0; i < n; i++) { 51 | yield* asyncGen('a', 1); 52 | } 53 | var use = Date.now() - start; 54 | console.log(" yield* asyncGen('a', 1) %d times, use: %sms, qps: %s", n, use, n / use * 1000); 55 | })(); 56 | 57 | // $ node --harmony benchmark/base.js 58 | // 59 | // thunkify benchmark 60 | // node version: v0.11.12, date: Thu Jul 10 2014 22:53:10 GMT+0800 (CST) 61 | // Starting... 62 | // 63 | // yield asyncThunk('a', 1) 1000000 times, use: 5578ms, qps: 179275.72606669058 64 | // yield asyncGen('a', 1) 1000000 times, use: 12610ms, qps: 79302.14115781126 65 | // yield* asyncGen('a', 1) 1000000 times, use: 6817ms, qps: 146692.09329617134 66 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 2 | var thunkify = require('./'); 3 | 4 | var Cal = function (a, b) { 5 | this.a = a; 6 | this.b = b; 7 | }; 8 | 9 | Cal.prototype.plus = function(callback) { 10 | var self = this; 11 | setTimeout(function () { 12 | callback(null, self.a + self.b); 13 | }, 5); 14 | }; 15 | 16 | Cal.prototype.minus = function (callback) { 17 | var self = this; 18 | setTimeout(function () { 19 | callback(null, self.a - self.b); 20 | }, 5); 21 | }; 22 | 23 | module.exports = Cal; 24 | 25 | exports.create1 = function (a, b) { 26 | return thunkify(new Cal(a, b)); 27 | }; 28 | 29 | // or 30 | exports.create2 = function (a, b) { 31 | var cal = new Cal(a, b); 32 | cal.plus = thunkify(cal.plus, cal); 33 | cal.minus = thunkify(cal.minus, cal); 34 | }; 35 | 36 | var cal1 = exports.create1(1, 2); 37 | 38 | cal1.plus()(function (err, res) { 39 | console.log('cal1 plus result is ', res); 40 | }); 41 | cal1.minus()(function (err, res) { 42 | console.log('cal1 minus result is ', res); 43 | }); 44 | 45 | var cal2 = exports.create1(1, 2); 46 | 47 | cal1.plus()(function (err, res) { 48 | console.log('cal2 plus result is ', res); 49 | }); 50 | cal1.minus()(function (err, res) { 51 | console.log('cal2 minus result is ', res); 52 | }); 53 | -------------------------------------------------------------------------------- /lib/event.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * thunkify-wrap - lib/thunkify.js 3 | * Copyright(c) 2014 dead_horse 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = function event(e, globalEvents) { 10 | globalEvents = globalEvents || ['end']; 11 | return function (endEvents) { 12 | var called = false; 13 | endEvents = endEvents || globalEvents; 14 | if (!Array.isArray(endEvents)) { 15 | endEvents = [endEvents]; 16 | } 17 | return function (done) { 18 | // clean 19 | function _done(err, data) { 20 | if (called) { 21 | return; 22 | } 23 | called = true; 24 | e.removeListener('error', _done); 25 | endEvents.forEach(function (name) { 26 | e.removeListener(name, end); 27 | }); 28 | done(err, data); 29 | } 30 | 31 | function end(data) { 32 | _done(null, data); 33 | } 34 | 35 | e.once('error', _done); 36 | endEvents.forEach(function (name) { 37 | e.once(name, end); 38 | }); 39 | }; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /lib/genify.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * thunkify-wrap - genify.js 3 | * Copyright(c) 2014 dead_horse 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var thunkify = require('./thunkify'); 14 | 15 | /** 16 | * create a generator function warp 17 | * so you can use yield* every where 18 | * 19 | * @return {[type]} [description] 20 | */ 21 | module.exports = function genify(fn, ctx) { 22 | if (isGeneratorFunction(fn)) { 23 | return fn; 24 | } 25 | 26 | function* genify() { 27 | var thunk = thunkify(fn); 28 | return yield thunk.apply(ctx || this, arguments); 29 | } 30 | return genify; 31 | }; 32 | 33 | function isGeneratorFunction(fn) { 34 | return typeof fn === 'function' && fn.constructor.name === 'GeneratorFunction'; 35 | } 36 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * thunkify-wrap - lib/index.js 3 | * MIT Licensed 4 | */ 5 | 6 | 'use strict'; 7 | var EventEmitter = require('events').EventEmitter; 8 | var thunkify = require('./thunkify'); 9 | var event = require('./event'); 10 | var enable = require('enable'); 11 | 12 | /** 13 | * Expose `thunkify()`. 14 | * @param {Function | Object} input 15 | * @param {Object} [ctx] 16 | * @param {Array} [methods] 17 | * @return {Function} 18 | * @api public 19 | */ 20 | 21 | module.exports = function (input, ctx, methods) { 22 | return wrapify(input, ctx, methods, thunkify); 23 | }; 24 | 25 | module.exports.event = event; 26 | 27 | if (enable.generator) { 28 | var genify = require('./genify'); 29 | 30 | /** 31 | * Expose `genify()`. 32 | * @param {Function | Object} input 33 | * @param {Object} [ctx] 34 | * @param {Array} [methods] 35 | * @return {Function} 36 | * @api public 37 | */ 38 | 39 | module.exports.genify = function (input, ctx, methods) { 40 | return wrapify(input, ctx, methods, genify); 41 | }; 42 | } 43 | 44 | /** 45 | * wrap a event object to a thunk 46 | * yield to wait the event emit `end`, `close`, `finish` or others 47 | * @param {Event} e 48 | * @param {Array} globalEvents 49 | * @return {Function} 50 | */ 51 | 52 | function wrapify(input, ctx, methods, wrapfn) { 53 | var type = typeof input; 54 | 55 | // thunkify function 56 | if (type === 'function') { 57 | return wrapfn(input, ctx); 58 | } 59 | 60 | // thunkify object 61 | if (type === 'object') { 62 | if (Array.isArray(ctx)) { 63 | methods = ctx; 64 | ctx = input; 65 | } 66 | 67 | if (typeof ctx === 'string') { 68 | methods = [ctx]; 69 | ctx = input; 70 | } 71 | 72 | ctx = ctx === undefined ? input : ctx; 73 | if (methods && methods.length) { 74 | methods.forEach(function (method) { 75 | input[method] = wrapfn(input[method], ctx); 76 | }); 77 | } else { 78 | // thunkify all methods in input 79 | for (var key in input) { 80 | if (typeof input[key] === 'function') { 81 | input[key] = wrapfn(input[key], ctx); 82 | } 83 | } 84 | } 85 | 86 | return input; 87 | } 88 | 89 | throw new TypeError('thunkify accept only `function` or `object`'); 90 | } 91 | -------------------------------------------------------------------------------- /lib/thunkify.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * thunkify-wrap - lib/thunkify.js 3 | * Copyright(c) 2014 dead_horse 4 | * MIT Licensed 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var slice = Array.prototype.slice; 10 | 11 | /** 12 | * Wrap a regular callback `fn` as a thunk. 13 | * 14 | * @param {Function} fn 15 | * @param {Object} [ctx] 16 | * @return {Function} 17 | */ 18 | 19 | module.exports = function thunkify(fn, ctx) { 20 | if (!fn) { 21 | return fn; 22 | } 23 | 24 | if (isGeneratorFunction(fn)) { 25 | return fn; 26 | } 27 | 28 | if (fn.toString() === thunk.toString()) { 29 | return fn; 30 | } 31 | function thunk() { 32 | var args = slice.call(arguments); 33 | var results; 34 | var called; 35 | var cb; 36 | 37 | args.push(function () { 38 | results = arguments; 39 | 40 | if (cb && !called) { 41 | called = true; 42 | cb.apply(this, results); 43 | } 44 | }); 45 | 46 | fn.apply(ctx || this, args); 47 | 48 | return function (fn) { 49 | cb = fn; 50 | 51 | if (results && !called) { 52 | called = true; 53 | fn.apply(ctx || this, results); 54 | } 55 | }; 56 | } 57 | return thunk; 58 | } 59 | 60 | function isGeneratorFunction(fn) { 61 | return typeof fn === 'function' && fn.constructor.name === 'GeneratorFunction'; 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thunkify-wrap", 3 | "version": "1.0.4", 4 | "repository": "dead-horse/node-thunkify-wrap", 5 | "description": "Turn callbacks, arrays, generators, generator functions, and promises into a thunk", 6 | "main": "lib/index.js", 7 | "files": [ 8 | "lib" 9 | ], 10 | "keywords": [ 11 | "thunk", 12 | "co", 13 | "generator", 14 | "generators", 15 | "promise" 16 | ], 17 | "dependencies": { 18 | "enable": "~3.2.0" 19 | }, 20 | "devDependencies": { 21 | "co-mocha": "*", 22 | "mocha": "*", 23 | "should": "*", 24 | "co": "*" 25 | }, 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /test/ctx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var thunkify = require('..'); 5 | var Cal = require('./support/cal'); 6 | 7 | describe('ctx', function () { 8 | describe('thunkify(fun) with ctx', function () { 9 | it('should work ok', function (done) { 10 | var cal1 = new Cal(3, 1); 11 | cal1.plus = thunkify(cal1.plus, cal1); 12 | cal1.minus = thunkify(cal1.minus, cal1); 13 | cal1.plus()(function (err, res) { 14 | assert(res === 4); 15 | cal1.minus()(function (err, res) { 16 | assert(res === 2); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | }); 22 | 23 | describe('thunkify.genify(fun) with ctx', function () { 24 | it('should work ok', function* () { 25 | var cal1 = new Cal(3, 1); 26 | cal1.plus = thunkify.genify(cal1.plus, cal1); 27 | cal1.minus = thunkify.genify(cal1.minus, cal1); 28 | var r = yield* cal1.plus(); 29 | assert(r === 4); 30 | r = yield* cal1.minus(); 31 | assert(r === 2); 32 | }); 33 | }); 34 | 35 | describe('thunkify(object) with ctx', function () { 36 | it('should work ok', function (done) { 37 | var cal2 = new Cal(2, 1); 38 | cal2 = thunkify(cal2); 39 | cal2.plus()(function (err, res) { 40 | assert(res === 3); 41 | cal2.minus()(function (err, res) { 42 | assert(res === 1); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | }); 48 | 49 | describe('thunkify.genify(object) with ctx', function () { 50 | it('should work ok', function* () { 51 | var cal2 = new Cal(2, 1); 52 | cal2 = thunkify.genify(cal2); 53 | var r = yield* cal2.plus(); 54 | assert(r === 3); 55 | r = yield* cal2.minus(); 56 | assert(r === 1); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/event.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var assert = require('assert'); 5 | var thunkify = require('..'); 6 | 7 | describe('thunkify.event(event)', function () { 8 | it('should ok by globalEvents', function (done) { 9 | var e = new EventEmitter(); 10 | var end = thunkify.event(e, 'finish'); 11 | e.on('data', function (data) { 12 | assert(data.foo === 'bar'); 13 | }); 14 | end()(function (err, data) { 15 | assert(!err); 16 | assert(data.success); 17 | done(); 18 | }); 19 | e.emit('data', {foo: 'bar'}); 20 | e.emit('finish', {success: true}); 21 | }); 22 | 23 | it('should ok by custom events', function (done) { 24 | var e = new EventEmitter(); 25 | var end = thunkify.event(e, 'finish'); 26 | e.on('data', function (data) { 27 | assert(data.foo === 'bar'); 28 | }); 29 | end('close')(function (err, data) { 30 | assert(!err); 31 | assert(data.success); 32 | done(); 33 | }); 34 | e.emit('data', {foo: 'bar'}); 35 | e.emit('close', {success: true}); 36 | }); 37 | 38 | it('should ok by custom events with array', function (done) { 39 | var e = new EventEmitter(); 40 | var end = thunkify.event(e, 'finish'); 41 | e.on('data', function (data) { 42 | assert(data.foo === 'bar'); 43 | }); 44 | end(['close', 'end'])(function (err, data) { 45 | assert(!err); 46 | assert(data.success); 47 | done(); 48 | }); 49 | e.emit('data', {foo: 'bar'}); 50 | e.emit('end', {success: true}); 51 | e.emit('end', {success: true}); // will ignore 52 | }); 53 | 54 | it('should error by error', function (done) { 55 | var e = new EventEmitter(); 56 | var end = thunkify.event(e, 'finish'); 57 | e.on('data', function (data) { 58 | assert(data.foo === 'bar'); 59 | }); 60 | end(['close', 'end'])(function (err, data) { 61 | assert(err.message === 'mock error'); 62 | done(); 63 | }); 64 | e.emit('data', {foo: 'bar'}); 65 | e.emit('error', new Error('mock error')); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/genify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var thunkify = require('..'); 4 | var assert = require('assert'); 5 | var fs = require('fs'); 6 | 7 | describe('thunkify.genify(fn)', function () { 8 | var readFile = fs.readFile; 9 | after(function () { 10 | fs.readFile = readFile; 11 | }); 12 | 13 | it('should work when sync', function* () { 14 | var read = function (file, fn) { 15 | fn(null, 'file: ' + file); 16 | }; 17 | 18 | read = thunkify.genify(read); 19 | 20 | assert('file: foo.txt' === (yield* read('foo.txt'))); 21 | }); 22 | 23 | it('should work when async', function* () { 24 | var read = function (file, fn) { 25 | setTimeout(function () { 26 | fn(null, 'file: ' + file); 27 | }, 5); 28 | }; 29 | 30 | read = thunkify.genify(read); 31 | 32 | assert('file: foo.txt' === (yield* read('foo.txt'))); 33 | }); 34 | 35 | it('should pass all results', function* () { 36 | var read = function (file, fn) { 37 | setTimeout(function () { 38 | fn(null, file[0], file[1]); 39 | }, 5); 40 | }; 41 | 42 | read = thunkify.genify(read); 43 | var r = yield* read('foo.txt'); 44 | assert('f' === r[0]); 45 | assert('o' === r[1]); 46 | }); 47 | 48 | it('should work with node methods', function* () { 49 | fs.readFile = thunkify.genify(fs.readFile); 50 | 51 | var buf = yield* fs.readFile('package.json'); 52 | assert(Buffer.isBuffer(buf)); 53 | 54 | var str = yield* fs.readFile('package.json', 'utf8'); 55 | assert('string' === typeof str); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var thunkify = require('..'); 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | 6 | describe('thunkify(fn)', function(){ 7 | it('should work when sync', function(done){ 8 | function read(file, fn) { 9 | fn(null, 'file: ' + file); 10 | } 11 | 12 | read = thunkify(read); 13 | 14 | read('foo.txt')(function(err, res){ 15 | assert(!err); 16 | assert('file: foo.txt' == res); 17 | done(); 18 | }); 19 | }) 20 | 21 | it('should work when async', function(done){ 22 | function read(file, fn) { 23 | setTimeout(function(){ 24 | fn(null, 'file: ' + file); 25 | }, 5); 26 | } 27 | 28 | read = thunkify(read); 29 | 30 | read('foo.txt')(function(err, res){ 31 | assert(!err); 32 | assert('file: foo.txt' == res); 33 | done(); 34 | }); 35 | }) 36 | 37 | it('should pass all results', function(done){ 38 | function read(file, fn) { 39 | setTimeout(function(){ 40 | fn(null, file[0], file[1]); 41 | }, 5); 42 | } 43 | 44 | read = thunkify(read); 45 | 46 | read('foo.txt')(function(err, a, b){ 47 | assert(!err); 48 | assert('f' == a); 49 | assert('o' == b); 50 | done(); 51 | }); 52 | }) 53 | 54 | it('should work with node methods', function(done){ 55 | fs.readFile = thunkify(fs.readFile); 56 | 57 | fs.readFile('package.json')(function(err, buf){ 58 | assert(!err); 59 | assert(Buffer.isBuffer(buf)); 60 | 61 | fs.readFile('package.json', 'utf8')(function(err, str){ 62 | assert(!err); 63 | assert('string' == typeof str); 64 | done(); 65 | }); 66 | }); 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var thunkify = require('..'); 5 | var Cal = require('./support/cal'); 6 | 7 | describe('methods', function () { 8 | describe('thunkify(object) with ctx', function () { 9 | it('should work ok', function (done) { 10 | var cal2 = new Cal(2, 1); 11 | cal2 = thunkify(cal2, ['plus']); 12 | cal2.plus()(function (err, res) { 13 | assert(res === 3); 14 | assert(!cal2.minus(function () {})); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('should ignore not exist method ok', function (done) { 20 | var cal2 = new Cal(2, 1); 21 | cal2 = thunkify(cal2, ['plus', 'not-exist']); 22 | cal2.plus()(function (err, res) { 23 | assert(res === 3); 24 | assert(!cal2.minus(function () {})); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | describe('thunkify.genify(object) with ctx', function () { 31 | it('should work ok', function* () { 32 | var cal2 = new Cal(2, 1); 33 | cal2 = thunkify.genify(cal2, ['plus']); 34 | assert((yield* cal2.plus()) === 3); 35 | assert(!cal2.minus(function () {})); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var thunkify = require('..'); 4 | var read = require('./support/read'); 5 | var assert = require('assert'); 6 | var copyRead = {}; 7 | for (var k in read) { 8 | copyRead[k] = read[k]; 9 | } 10 | 11 | describe('thunkify(object)', function(){ 12 | before(function () { 13 | thunkify(read); 14 | // thunkify twice still ok 15 | thunkify(read); 16 | 17 | thunkify.genify(copyRead); 18 | // thunkify.genify twice still ok 19 | thunkify.genify(copyRead); 20 | }); 21 | 22 | it('should work when sync', function(done){ 23 | read.sync('foo.txt')(function(err, res){ 24 | assert(!err); 25 | assert('file: foo.txt' === res); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should work when async', function(done){ 31 | read.async('foo.txt')(function(err, res){ 32 | assert(!err); 33 | assert('file: foo.txt' === res); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should pass all results', function(done){ 39 | read.multi('foo.txt')(function(err, a, b){ 40 | assert(!err); 41 | assert('f' === a); 42 | assert('o' === b); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should nothing happend to notFunc', function () { 48 | assert(read.notFunc, 'notFunc'); 49 | }); 50 | 51 | describe('genify()', function () { 52 | it('should work when sync', function* () { 53 | var res = yield* copyRead.sync('foo.txt'); 54 | assert('file: foo.txt' === res); 55 | }); 56 | 57 | it('should work when async', function* () { 58 | var res = yield* copyRead.async('foo.txt'); 59 | assert('file: foo.txt' === res); 60 | }); 61 | 62 | it('should pass all results', function* () { 63 | var r = yield* copyRead.multi('foo.txt'); 64 | assert('f' === r[0]); 65 | assert('o' === r[1]); 66 | }); 67 | 68 | it('should nothing happend to notFunc', function () { 69 | assert(copyRead.notFunc, 'notFunc'); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/support/cal.js: -------------------------------------------------------------------------------- 1 | 2 | var Cal = function (a, b) { 3 | this.a = a; 4 | this.b = b; 5 | }; 6 | 7 | Cal.prototype.plus = function(callback) { 8 | var self = this; 9 | setTimeout(function () { 10 | callback(null, self.a + self.b); 11 | }, 5); 12 | }; 13 | 14 | Cal.prototype.minus = function (callback) { 15 | var self = this; 16 | setTimeout(function () { 17 | callback(null, self.a - self.b); 18 | }, 5); 19 | }; 20 | 21 | module.exports = Cal; 22 | -------------------------------------------------------------------------------- /test/support/read.js: -------------------------------------------------------------------------------- 1 | 2 | exports.sync = function (file, fn) { 3 | fn(null, 'file: ' + file); 4 | }; 5 | 6 | exports.async = function (file, fn) { 7 | setTimeout(function(){ 8 | fn(null, 'file: ' + file); 9 | }, 5); 10 | }; 11 | 12 | exports.multi = function (file, fn) { 13 | setTimeout(function(){ 14 | fn(null, file[0], file[1]); 15 | }, 5); 16 | }; 17 | 18 | exports.notFunc = 'notFunc'; 19 | --------------------------------------------------------------------------------