├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── index.js ├── package.json └── test └── after-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .monitor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.9 6 | - 0.10 7 | - 0.12 8 | - 4.2.4 9 | - 5.4.1 10 | - iojs-1 11 | - iojs-2 12 | - iojs-3 13 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Raynos. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # After [![Build Status][1]][2] 2 | 3 | Invoke callback after n calls 4 | 5 | ## Status: production ready 6 | 7 | ## Example 8 | 9 | ```js 10 | var after = require("after") 11 | var db = require("./db") // some db. 12 | 13 | var updateUser = function (req, res) { 14 | // use after to run two tasks in parallel, 15 | // namely get request body and get session 16 | // then run updateUser with the results 17 | var next = after(2, updateUser) 18 | var results = {} 19 | 20 | getJSONBody(req, res, function (err, body) { 21 | if (err) return next(err) 22 | 23 | results.body = body 24 | next(null, results) 25 | }) 26 | 27 | getSessionUser(req, res, function (err, user) { 28 | if (err) return next(err) 29 | 30 | results.user = user 31 | next(null, results) 32 | }) 33 | 34 | // now do the thing! 35 | function updateUser(err, result) { 36 | if (err) { 37 | res.statusCode = 500 38 | return res.end("Unexpected Error") 39 | } 40 | 41 | if (!result.user || result.user.role !== "admin") { 42 | res.statusCode = 403 43 | return res.end("Permission Denied") 44 | } 45 | 46 | db.put("users:" + req.params.userId, result.body, function (err) { 47 | if (err) { 48 | res.statusCode = 500 49 | return res.end("Unexpected Error") 50 | } 51 | 52 | res.statusCode = 200 53 | res.end("Ok") 54 | }) 55 | } 56 | } 57 | ``` 58 | 59 | ## Naive Example 60 | 61 | ```js 62 | var after = require("after") 63 | , next = after(3, logItWorks) 64 | 65 | next() 66 | next() 67 | next() // it works 68 | 69 | function logItWorks() { 70 | console.log("it works!") 71 | } 72 | ``` 73 | 74 | ## Example with error handling 75 | 76 | ```js 77 | var after = require("after") 78 | , next = after(3, logError) 79 | 80 | next() 81 | next(new Error("oops")) // logs oops 82 | next() // does nothing 83 | 84 | // This callback is only called once. 85 | // If there is an error the callback gets called immediately 86 | // this avoids the situation where errors get lost. 87 | function logError(err) { 88 | console.log(err) 89 | } 90 | ``` 91 | 92 | ## Installation 93 | 94 | `npm install after` 95 | 96 | ## Tests 97 | 98 | `npm test` 99 | 100 | ## Contributors 101 | 102 | - Raynos 103 | - defunctzombie 104 | 105 | ## MIT Licensed 106 | 107 | [1]: https://secure.travis-ci.org/Raynos/after.png 108 | [2]: http://travis-ci.org/Raynos/after 109 | [3]: http://raynos.org/blog/2/Flow-control-in-node.js 110 | [4]: http://stackoverflow.com/questions/6852059/determining-the-end-of-asynchronous-operations-javascript/6852307#6852307 111 | [5]: http://stackoverflow.com/questions/6869872/in-javascript-what-are-best-practices-for-executing-multiple-asynchronous-functi/6870031#6870031 112 | [6]: http://stackoverflow.com/questions/6864397/javascript-performance-long-running-tasks/6889419#6889419 113 | [7]: http://stackoverflow.com/questions/6597493/synchronous-database-queries-with-node-js/6620091#6620091 114 | [8]: http://github.com/Raynos/iterators 115 | [9]: http://github.com/Raynos/composite 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = after 2 | 3 | function after(count, callback, err_cb) { 4 | var bail = false 5 | err_cb = err_cb || noop 6 | proxy.count = count 7 | 8 | return (count === 0) ? callback() : proxy 9 | 10 | function proxy(err, result) { 11 | if (proxy.count <= 0) { 12 | throw new Error('after called too many times') 13 | } 14 | --proxy.count 15 | 16 | // after first error, rest are passed to err_cb 17 | if (err) { 18 | bail = true 19 | callback(err) 20 | // future error callbacks will go to error handler 21 | callback = err_cb 22 | } else if (proxy.count === 0 && !bail) { 23 | callback(null, result) 24 | } 25 | } 26 | } 27 | 28 | function noop() {} 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "after", 3 | "description": "after - tiny flow control", 4 | "version": "0.8.2", 5 | "author": "Raynos ", 6 | "contributors": [ 7 | { 8 | "name": "Raynos", 9 | "email": "raynos2@gmail.com", 10 | "url": "http://raynos.org" 11 | } 12 | ], 13 | "scripts": { 14 | "test": "mocha --ui tdd --reporter spec test/*.js" 15 | }, 16 | "devDependencies": { 17 | "mocha": "~1.8.1" 18 | }, 19 | "keywords": [ 20 | "flowcontrol", 21 | "after", 22 | "flow", 23 | "control", 24 | "arch" 25 | ], 26 | "license": "MIT", 27 | "repository": "git://github.com/Raynos/after.git" 28 | } 29 | -------------------------------------------------------------------------------- /test/after-test.js: -------------------------------------------------------------------------------- 1 | /*global suite, test*/ 2 | 3 | var assert = require("assert") 4 | , after = require("../") 5 | 6 | test("exists", function () { 7 | assert(typeof after === "function", "after is not a function") 8 | }) 9 | 10 | test("after when called with 0 invokes", function (done) { 11 | after(0, done) 12 | }); 13 | 14 | test("after 1", function (done) { 15 | var next = after(1, done) 16 | next() 17 | }) 18 | 19 | test("after 5", function (done) { 20 | var next = after(5, done) 21 | , i = 5 22 | 23 | while (i--) { 24 | next() 25 | } 26 | }) 27 | 28 | test("manipulate count", function (done) { 29 | var next = after(1, done) 30 | , i = 5 31 | 32 | next.count = i 33 | while (i--) { 34 | next() 35 | } 36 | }) 37 | 38 | test("after terminates on error", function (done) { 39 | var next = after(2, function(err) { 40 | assert.equal(err.message, 'test'); 41 | done(); 42 | }) 43 | next(new Error('test')) 44 | next(new Error('test2')) 45 | }) 46 | 47 | test('gee', function(done) { 48 | done = after(2, done) 49 | 50 | function cb(err) { 51 | assert.equal(err.message, 1); 52 | done() 53 | } 54 | 55 | var next = after(3, cb, function(err) { 56 | assert.equal(err.message, 2) 57 | done() 58 | }); 59 | 60 | next() 61 | next(new Error(1)) 62 | next(new Error(2)) 63 | }) 64 | 65 | test('eee', function(done) { 66 | done = after(3, done) 67 | 68 | function cb(err) { 69 | assert.equal(err.message, 1); 70 | done() 71 | } 72 | 73 | var next = after(3, cb, function(err) { 74 | assert.equal(err.message, 2) 75 | done() 76 | }); 77 | 78 | next(new Error(1)) 79 | next(new Error(2)) 80 | next(new Error(2)) 81 | }) 82 | 83 | test('gge', function(done) { 84 | function cb(err) { 85 | assert.equal(err.message, 1); 86 | done() 87 | } 88 | 89 | var next = after(3, cb, function(err) { 90 | // should not happen 91 | assert.ok(false); 92 | }); 93 | 94 | next() 95 | next() 96 | next(new Error(1)) 97 | }) 98 | 99 | test('egg', function(done) { 100 | function cb(err) { 101 | assert.equal(err.message, 1); 102 | done() 103 | } 104 | 105 | var next = after(3, cb, function(err) { 106 | // should not happen 107 | assert.ok(false); 108 | }); 109 | 110 | next(new Error(1)) 111 | next() 112 | next() 113 | }) 114 | 115 | test('throws on too many calls', function(done) { 116 | var next = after(1, done); 117 | next() 118 | assert.throws(next, /after called too many times/); 119 | }); 120 | 121 | --------------------------------------------------------------------------------