├── .gitignore ├── LICENSE ├── README.md ├── callbacks ├── 01-sync-chain.js ├── 02-conditionals.js ├── 03-looping.js ├── 04-errors.js ├── 05-kitchen-sink.js └── README.md ├── maxogden-style └── 05-kitchen-sink │ ├── index.js │ ├── package.json │ ├── readme.md │ └── test.js ├── monocle-js ├── 01-sync-chain.js ├── 02-conditionals.js ├── 03-looping.js ├── 04-errors.js ├── 05-kitchen-sink.js └── README.md ├── package.json ├── promises-bluebird-topdown ├── 05-kitchen-sink-es6.js ├── 05-kitchen-sink.js └── README.md └── promises-bluebird ├── 01-sync-chain.js ├── 02-conditionals.js ├── 03-looping.js ├── 04-errors.js ├── 05-kitchen-sink.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | testfile.txt 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Async JS Control Flow Showcase 2 | ============================== 3 | 4 | This is a project designed to showcase different ways to handle asynchronous 5 | control flow in Javascript. If you're familiar with this concept, you might 6 | want to skip to the [list of projects](#alternatives) or the [description of 7 | callback issues](#issues). 8 | 9 | Note that this is meant to be an informational, not a competitive, guide to 10 | all the approaches to flow control on offer. 11 | 12 | Background 13 | ---------- 14 | Javascript applications, including Node.js-based software, are most frequently 15 | structured around event-driven or asynchronous methods. It's possible that 16 | a function `foo` or `bar` could kick off some logic that is not yet complete 17 | when the functions themselves complete. One common example of this is 18 | Javascript's `setTimeout()`. `setTimeout`, as the name implies, doesn't 19 | actually pause execution for a designated amount of time. Instead, it schedules 20 | execution for a later time, and meanwhile returns control to the script: 21 | 22 | ```js 23 | var done = function() { 24 | console.log("set timeout finished!"); 25 | }; 26 | 27 | setTimeout(done, 1000); 28 | console.log("hello world"); 29 | ``` 30 | 31 | The output of this script is: 32 | 33 | ``` 34 | hello world 35 | set timeout finished! 36 | ``` 37 | 38 | Callbacks 39 | --------- 40 | Node.js has generalized this asynchronous control flow idea through the use of 41 | callbacks. This is a convention whereby an asynchronous function takes 42 | a parameter which is itself a function. This function, called a callback, is 43 | then called whenever the asynchronous result is ready. In Node, callbacks are 44 | called with the convention that an Error object is passed as the first 45 | parameter for use in error handling, and further results are sent as subsequent 46 | parameters. Let's take a look at an example: 47 | 48 | 49 | ```js 50 | var fs = require('fs'); 51 | 52 | var readFileCallback = function(err, fileData) { 53 | if (err) { 54 | console.log("We got an error! Oops!"); 55 | return; 56 | } 57 | console.log(fileData.toString('utf8')); 58 | }; 59 | 60 | fs.readFile('/path/to/my/file', readFileCallback); 61 | ``` 62 | 63 | In this example, we use a callback-based method `fs.readFile()` to get the 64 | contents of a file. The function `readFileCallback` is called when the file 65 | data has been retrieved. If there was an error, say because the file doesn't 66 | exist, we get that as the first parameter, and can log a helpful message. 67 | Otherwise, we can display the file contents. 68 | 69 | Because we can use anonymous functions, it's much more common to see code that 70 | looks like this: 71 | 72 | ```js 73 | var fs = require('fs'); 74 | 75 | fs.readFile('/path/to/my/file', function(err, fileData) { 76 | if (err) { 77 | console.log("We got an error! Oops!"); 78 | return; 79 | } 80 | console.log(fileData.toString('utf8')); 81 | }); 82 | ``` 83 | 84 | This architecture can be quite powerful because it is non-blocking. We could 85 | easily read two files at the same time just by putting calls to `fs.readFile` 86 | one after the other: 87 | 88 | ```js 89 | var fs = require('fs'); 90 | 91 | var readFileCallback = function(err, fileData) { 92 | if (err) { 93 | console.log("We got an error! Oops!"); 94 | return; 95 | } 96 | console.log(fileData.toString('utf8')); 97 | }; 98 | 99 | fs.readFile('/path/to/my/file', readFileCallback); 100 | fs.readFile('/path/to/another/file', readFileCallback); 101 | ``` 102 | 103 | In this example, the file contents will be printed out in an undetermined order 104 | because they're kicked off at roughly the same time, and their callbacks will 105 | be called when the system is done reading each one, which could vary based on 106 | many factors. 107 | 108 | The impact of callbacks 109 | ----------------------- 110 | There are a number of issues that arise as part of a callback-based 111 | architecture. These issues range from the aesthetic to the practical (e.g., 112 | some argue callbacks lead to less readable or less maintainable code). 113 | 114 | ### Rightward drift 115 | 116 | It often happens that you want to run a number of asynchronous methods, one 117 | after the other. In this case, each method must be called in the callback of 118 | the previous method. Using the anonymous function strategy detailed above, you 119 | end up with code that looks like this: 120 | 121 | ```js 122 | asyncFn1(function() { 123 | asyncFn2(function() { 124 | asyncFn3(function() { 125 | asyncFn4(function() { 126 | console.log("We're done!"); 127 | }); 128 | }); 129 | }); 130 | }); 131 | ``` 132 | 133 | To some people, once you start filling these functions out with their own 134 | particular logic, it's very easy to lose track of where you are in the logical 135 | flow. 136 | 137 | ### Branching logic 138 | 139 | Sometimes you might want to call a function `bar()` only if the result of 140 | another function `foo()` matches some criteria. If these are synchronous 141 | functions, the logic looks like this: 142 | 143 | ```js 144 | var res = foo(); 145 | if (res === "5") { 146 | res = bar(res); 147 | } 148 | res = baz(res); 149 | console.log("After transforming, res is " + res); 150 | ``` 151 | 152 | If `foo` and `bar` are asynchronous functions, however, it gets a little more 153 | complicated. One option is to duplicate code: 154 | 155 | ```js 156 | foo(function(res) { 157 | if (res === "5") { 158 | bar(res, function(res2) { 159 | baz(res2, function(res3) { 160 | console.log("After transforming, res is " + res3); 161 | }); 162 | }); 163 | return; 164 | } 165 | baz(res, function(res2) { 166 | console.log("After transforming, res is " + res2); 167 | }); 168 | }); 169 | ``` 170 | 171 | In this case, we've duplicated the calls to `baz`. An alternative is to create 172 | a `next` function that encapsulates the `baz` call and subsequent log 173 | statement: 174 | 175 | ```js 176 | var next = function(res) { 177 | baz(res, function(res2) { 178 | console.log("After transforming, res is " + res2); 179 | }); 180 | }; 181 | 182 | foo(function(res) { 183 | if (res === "5") { 184 | bar(res, function(res2) { 185 | next(res2); 186 | }); 187 | return; 188 | } 189 | next(res); 190 | }); 191 | ``` 192 | 193 | This is more DRY, but at the cost of creating a function whose only purpose is 194 | to continue the logical flow of the code, called in multiple places. 195 | 196 | ### Looping 197 | 198 | If you need to perform an async method on a number of objects, it can be 199 | a little mind-bending. Synchronously, we can do something like this: 200 | 201 | ```js 202 | var collection = [objA, objB, objC]; 203 | var response = []; 204 | for (var i = 0; i < collection.length; i++) { 205 | response.push(transformMyObject(collection[i])); 206 | } 207 | console.log(response); 208 | ``` 209 | 210 | If `transformMyObject` is actually asynchronous, and we need to transform each 211 | object one after the other, we need to do something more like this: 212 | 213 | ```js 214 | var collection = [objA, objB, objC]; 215 | var response = []; 216 | var doTransform = function() { 217 | var obj = collection.unshift(); 218 | if (typeof obj === "undefined") { 219 | console.log(response); 220 | } else { 221 | transformMyObject(obj, function(err, newObj) { 222 | response.push(newObj); 223 | doTransform(); 224 | }); 225 | } 226 | }; 227 | doTransform(); 228 | ``` 229 | 230 | ### Error handling 231 | 232 | You can't use Javascript's basic error handling techniques (try/catch) to 233 | handle errors in callbacks, even if they're defined in the same scope. In other 234 | words, this doesn't work: 235 | 236 | ```js 237 | var crashyFunction = function(cb) { 238 | throw new Error("uh oh!"); 239 | }; 240 | 241 | var runFooBar = function(cb) { 242 | foo(function() { 243 | crashyFunction(function() { 244 | bar(function() { 245 | cb(); 246 | }); 247 | }); 248 | }); 249 | }; 250 | 251 | try { 252 | runFooBar(function() { 253 | console.log("We're done"); 254 | }); 255 | } catch (err) { 256 | console.log(err.message); 257 | } 258 | ``` 259 | 260 | This is why Node.js uses a convention of passing errors into callbacks so they 261 | can be handled: 262 | 263 | ```js 264 | var crashyFunction = function(cb) { 265 | try { 266 | throw new Error("uh oh!"); 267 | } catch (e) { 268 | cb(e); 269 | } 270 | }; 271 | 272 | var runFooBar = function(cb) { 273 | foo(function(err) { 274 | if (err) return cb(err); 275 | crashyFunction(function(err) { 276 | if (err) return cb(err); 277 | bar(function(err) { 278 | if (err) return cb(err); 279 | cb(); 280 | }); 281 | }); 282 | }); 283 | }; 284 | 285 | runFooBar(function(err) { 286 | if (err) return console.log(err.message); 287 | console.log("We're done!"); 288 | }); 289 | 290 | ``` 291 | 292 | As you can see, the result of this is that we have to check the error state in 293 | every callback so we can short-circuit the chain and pass the error to the 294 | top-level callback. This is a bit redundant at best. 295 | 296 | Node.js now has domains, which makes this problem a little easier to 297 | handle. 298 | 299 | Alternatives to callbacks 300 | ------------------------- 301 | Of course, callbacks aren't the only way to do asynchronous control flow. There 302 | are many helpful libraries that make using callbacks less prone to the issues 303 | above. There are also alternatives which aren't callback-based at all, though 304 | they might rely on callbacks under the hood. The point of this project is to 305 | enable a side-by-side comparison of these approaches. 306 | 307 | The [callbacks directory](callbacks/) of this repo has a number of reference 308 | Javascript files which demonstrate in code how callback-based flow control 309 | works. These files can all be run using Node.js. 310 | 311 | The following projects have implemented, or are in the process of implementing, 312 | revisions of the reference scripts using their particular approach to 313 | asynchronous control flow. Please check them out, read the directory's README 314 | to see how they address the issues above, run the code, and make an informed 315 | decision about which approach to adopt in your own projects! 316 | 317 | Hint: take a look at the "kitchen sink" example file in each of the projects to 318 | see how they handle all the issues in one script. 319 | 320 | * [Callbacks](callbacks/) (i.e., the "standard" or "naive" approach above) 321 | 322 | Other approaches: 323 | 324 | * [Bluebird](promises-bluebird/) [[project](https://github.com/petkaantonov/bluebird)] 325 | * Caolan/async [[project](https://github.com/caolan/async)] 326 | * Co [[project](https://github.com/visionmedia/co)] 327 | * IcedCoffeeScript [[project](http://maxtaco.github.io/coffee-script/)] 328 | * [Max Ogden style callbacks](maxogden-style/) [[website](http://callbackhell.com)] 329 | * [Monocle-js](monocle-js/) [[project](https://github.com/jlipps/monocle-js)] 330 | * Q [[project](https://github.com/kriskowal/q)] 331 | * When [[project](https://github.com/cujojs/when)] 332 | * bevacqua/contra [[project](https://github.com/bevacqua/contra)] 333 | 334 | 335 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/jlipps/async-showcase/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 336 | 337 | -------------------------------------------------------------------------------- /callbacks/01-sync-chain.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , should = require('should') 4 | , testFile = path.resolve("..", "testFile.txt"); 5 | 6 | fs.writeFile(testFile, "Hello World", function(err) { 7 | setTimeout(function() { 8 | fs.appendFile(testFile, "!", function(err) { 9 | fs.appendFile(testFile, " :-)", function(err) { 10 | fs.readFile(testFile, function(err, data) { 11 | data = data.toString(); 12 | data.should.equal("Hello World! :-)"); 13 | fs.unlink(testFile, function(err) { 14 | console.log("Done!"); 15 | }); 16 | }); 17 | }); 18 | }); 19 | }, 500); 20 | }); 21 | -------------------------------------------------------------------------------- /callbacks/02-conditionals.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , should = require('should') 4 | , testFile = path.resolve("..", "testFile.txt"); 5 | 6 | fs.stat(testFile, function(err) { 7 | 8 | var next = function() { 9 | fs.writeFile(testFile, "Hello World", function(err) { 10 | setTimeout(function() { 11 | var evenness = ''; 12 | 13 | var next2 = function() { 14 | fs.appendFile(testFile, "!", function(err) { 15 | fs.appendFile(testFile, " " + evenness, function(err) { 16 | fs.readFile(testFile, function(err, data) { 17 | data = data.toString(); 18 | data.should.equal("Hello World! " + evenness); 19 | fs.unlink(testFile, function(err) { 20 | console.log("Done!"); 21 | }); 22 | }); 23 | }); 24 | }); 25 | }; 26 | 27 | if (Date.now() % 2 === 0) { 28 | evenness = 'Time was even, doing nothing! '; 29 | next2(); 30 | } else { 31 | evenness = 'Time was odd, need to wait a ms...'; 32 | setTimeout(next2, 1); 33 | } 34 | }, 500); 35 | }); 36 | }; 37 | 38 | if (err) { 39 | // file doesn't exist 40 | next(); 41 | } else { 42 | fs.unlink(testFile, function(err) { 43 | next(); 44 | }); 45 | } 46 | 47 | }); 48 | 49 | 50 | -------------------------------------------------------------------------------- /callbacks/03-looping.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , should = require('should') 4 | , testFile = path.resolve("..", "testFile.txt"); 5 | 6 | fs.writeFile(testFile, "Hello World!", function(err) { 7 | var maxNum = 10; 8 | var curNum = 0; 9 | var writeNums = function(cb) { 10 | fs.appendFile(testFile, " " + curNum, function(err) { 11 | curNum++; 12 | if (curNum <= maxNum) { 13 | writeNums(cb); 14 | } else { 15 | cb(); 16 | } 17 | }); 18 | }; 19 | writeNums(function() { 20 | fs.readFile(testFile, function(err, data) { 21 | data = data.toString(); 22 | data.should.equal("Hello World! 0 1 2 3 4 5 6 7 8 9 10"); 23 | fs.unlink(testFile, function(err) { 24 | console.log("Done!"); 25 | }); 26 | }); 27 | }); 28 | }); 29 | 30 | 31 | -------------------------------------------------------------------------------- /callbacks/04-errors.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , should = require('should') 4 | , testFile = path.resolve("..", "testFile.txt"); 5 | 6 | var gratuitousFileFunction = function(cb) { 7 | fs.writeFile(testFile, "Hello World", function(err) { 8 | if (err) return cb(err); 9 | setTimeout(function() { 10 | fs.appendFile(testFile, "!", function(err) { 11 | if (err) return cb(err); 12 | fs.appendFile(testFile, " :-)", function(err) { 13 | if (err) return cb(err); 14 | fs.readFile(testFile, function(err, data) { 15 | if (err) return cb(err); 16 | data = data.toString(); 17 | data.should.equal("Hello World! :-)"); 18 | fs.unlink(testFile, cb); 19 | }); 20 | }); 21 | }); 22 | }, 500); 23 | }); 24 | }; 25 | 26 | gratuitousFileFunction(function(err) { 27 | if (err) return console.log(err); 28 | console.log("Done!"); 29 | }); 30 | -------------------------------------------------------------------------------- /callbacks/05-kitchen-sink.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , should = require('should') 4 | , testFile = path.resolve("..", "testFile.txt"); 5 | 6 | var gratuitousFileFunction = function(cb) { 7 | fs.stat(testFile, function(err) { 8 | 9 | var next = function() { 10 | fs.writeFile(testFile, "Hello World", function(err) { 11 | if (err) return cb(err); 12 | setTimeout(function() { 13 | var evenness = ''; 14 | 15 | var next2 = function() { 16 | fs.appendFile(testFile, "!", function(err) { 17 | if (err) return cb(err); 18 | fs.appendFile(testFile, " " + evenness, function(err) { 19 | if (err) return cb(err); 20 | var maxNum = 10; 21 | var curNum = 0; 22 | var writeNums = function(cb) { 23 | fs.appendFile(testFile, " " + curNum, function(err) { 24 | if (err) return cb(err); 25 | curNum++; 26 | if (curNum <= maxNum) { 27 | writeNums(cb); 28 | } else { 29 | cb(); 30 | } 31 | }); 32 | }; 33 | writeNums(function(err) { 34 | if (err) return err; 35 | fs.readFile(testFile, function(err, data) { 36 | if (err) return cb(err); 37 | data = data.toString(); 38 | data.should.equal("Hello World! " + evenness + 39 | " 0 1 2 3 4 5 6 7 8 9 10"); 40 | fs.unlink(testFile, cb); 41 | }); 42 | }); 43 | }); 44 | }); 45 | }; 46 | 47 | if (Date.now() % 2 === 0) { 48 | evenness = 'Time was even, doing nothing! '; 49 | next2(); 50 | } else { 51 | evenness = 'Time was odd, need to wait a ms...'; 52 | setTimeout(next2, 1); 53 | } 54 | }, 500); 55 | }); 56 | }; 57 | 58 | if (err) { 59 | // file doesn't exist 60 | next(); 61 | } else { 62 | fs.unlink(testFile, function(err) { 63 | if (err) return cb(err); 64 | next(); 65 | }); 66 | } 67 | 68 | }); 69 | }; 70 | 71 | gratuitousFileFunction(function(err) { 72 | if (err) return console.log(err); 73 | console.log("Done!"); 74 | }); 75 | -------------------------------------------------------------------------------- /callbacks/README.md: -------------------------------------------------------------------------------- 1 | Callbacks Reference Scripts 2 | =========================== 3 | 4 | These javascript files show how callbacks are used in Node to control asynchronous function execution. 5 | 6 | Other projects should use these files as a reference in order to create their own examples showcasing their approach. 7 | -------------------------------------------------------------------------------- /maxogden-style/05-kitchen-sink/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | 3 | module.exports = removeTestFile 4 | 5 | function removeTestFile(testFile, cb) { 6 | fs.stat(testFile, function(err) { 7 | if (err) { 8 | // file doesn't exist 9 | createTestFile(testFile, cb); 10 | } else { 11 | fs.unlink(testFile, function(err) { 12 | if (err) return cb(err); 13 | createTestFile(testFile, cb); 14 | }); 15 | } 16 | 17 | }) 18 | } 19 | 20 | function createTestFile(testFile, cb) { 21 | fs.writeFile(testFile, "Hello World", function(err) { 22 | if (err) return cb(err); 23 | var evenness = ''; 24 | if (Date.now() % 2 === 0) { 25 | evenness = 'Time was even, doing nothing! '; 26 | appendToTestFile(testFile, evenness, cb); 27 | } else { 28 | evenness = 'Time was odd, need to wait a ms...'; 29 | setTimeout(function() { 30 | appendToTestFile(testFile, evenness, cb); 31 | }, 1); 32 | } 33 | }); 34 | }; 35 | 36 | function appendToTestFile(testFile, evenness, cb) { 37 | fs.appendFile(testFile, "! " + evenness, function(err) { 38 | if (err) return cb(err); 39 | var maxNum = 10; 40 | var curNum = 0; 41 | 42 | writeNums(function(err) { 43 | if (err) return err; 44 | fs.readFile(testFile, function(err, data) { 45 | if (err) return cb(err); 46 | data = data.toString(); 47 | fs.unlink(testFile, function(err) { 48 | cb(err, data, evenness) 49 | }); 50 | }); 51 | }); 52 | 53 | function writeNums(cb) { 54 | fs.appendFile(testFile, " " + curNum, function(err) { 55 | if (err) return cb(err); 56 | curNum++; 57 | if (curNum <= maxNum) { 58 | writeNums(cb); 59 | } else { 60 | cb(); 61 | } 62 | }); 63 | }; 64 | }); 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /maxogden-style/05-kitchen-sink/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gratuitous-file-function", 3 | "version": "0.0.1", 4 | "description": "a gratuitous file function", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "author": "max ogden", 10 | "license": "BSD-2-Clause", 11 | "dependencies": { 12 | "should": "~2.1.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /maxogden-style/05-kitchen-sink/readme.md: -------------------------------------------------------------------------------- 1 | # gratuitous file function 2 | 3 | A modular version of https://github.com/jlipps/async-showcase/blob/master/callbacks/05-kitchen-sink.js using callbacks. 4 | 5 | This takes the original naive version from the `/callbacks` folder and applies the steps from http://callbackhell.com/. 6 | 7 | First read the original, then read this version, and form your own opinion. 8 | 9 | Some additional insight regarding my stance on async coding: 10 | 11 | - I don't expect async JS code to read top-to-bottom like blocking I/O code. Attempts to do this (IMO) are like fitting a square peg in a round hole. 12 | - This employs the node module pattern, e.g. it has a package.json, a readme with instructions, a generic module and a test showing how to consume the module API. 13 | - There are no external flow control libraries used, only callbacks. The different between this one and the original callback implementation is simply coding style. I prefer callbacks mainly for [performance reasons](http://blog.trevnorris.com/2013/08/callbacks-what-i-said-was.html) (many of my projects in node are performance critical, so to speak) but also because I have found them easier to debug when things go wrong (Murphy's law). 14 | - This is partially covered in callbackhell.com, but I find that naming anonymous functions and taking advantage of function hoisting go a long way towards writing readable async code. Turning your code into a module also forces you to write nice APIs. 15 | - My own preference is to write simple code over clever code, to avoid messy abstractions and to publish as many node modules as possible to NPM! 16 | 17 | ### install and run 18 | 19 | ``` 20 | cd maxogden-style/05-kitchen-sink 21 | npm install 22 | npm test 23 | ``` 24 | 25 | ### usage 26 | 27 | ``` 28 | var gff = require('maxogden-style/05-kitchen-sink') 29 | var fileToUse = 'some-filename.txt' 30 | gff(fileToUse, cb) 31 | ``` 32 | 33 | `fileToUse` can either exist or not 34 | `cb` will be called with `(err, data, evenness)` 35 | -------------------------------------------------------------------------------- /maxogden-style/05-kitchen-sink/test.js: -------------------------------------------------------------------------------- 1 | var should = require('should') 2 | var path = require('path') 3 | var testFile = path.resolve("testFile.txt") 4 | var gratuitousFileFunction = require('./') 5 | 6 | gratuitousFileFunction(testFile, function(err, data, evenness) { 7 | if (err) return console.log(err) 8 | var result = "Hello World! " + evenness + " 0 1 2 3 4 5 6 7 8 9 10" 9 | data.should.equal(result) 10 | console.log("Done!") 11 | }) 12 | -------------------------------------------------------------------------------- /monocle-js/01-sync-chain.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('monocle-fs') 3 | , monocle = require('monocle-js') 4 | , sleep = monocle.utils.sleep 5 | , should = require('should') 6 | , testFile = path.resolve("..", "testFile.txt"); 7 | 8 | monocle.run(function*() { 9 | yield fs.writeFile(testFile, "Hello World"); 10 | yield sleep(0.5); 11 | yield fs.appendFile(testFile, "!"); 12 | yield fs.appendFile(testFile, " :-)"); 13 | var data = yield fs.readFile(testFile); 14 | data = data.toString(); 15 | data.should.equal("Hello World! :-)"); 16 | yield fs.unlink(testFile); 17 | console.log("Done!"); 18 | }); 19 | -------------------------------------------------------------------------------- /monocle-js/02-conditionals.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('monocle-fs') 3 | , monocle = require('monocle-js') 4 | , sleep = monocle.utils.sleep 5 | , should = require('should') 6 | , testFile = path.resolve("..", "testFile.txt"); 7 | 8 | monocle.run(function*() { 9 | try { 10 | yield fs.stat(testFile); 11 | yield fs.unlink(testFile); 12 | } catch (e) {} 13 | yield fs.writeFile(testFile, "Hello World"); 14 | yield fs.appendFile(testFile, "!"); 15 | var evenness = ''; 16 | if (Date.now() % 2 === 0) { 17 | evenness = 'Time was even, doing nothing! '; 18 | } else { 19 | evenness = 'Time was odd, need to wait a ms...'; 20 | yield sleep(0.001); 21 | } 22 | yield sleep(0.5); 23 | yield fs.appendFile(testFile, " " + evenness); 24 | var data = yield fs.readFile(testFile); 25 | data = data.toString(); 26 | data.should.equal("Hello World! " + evenness); 27 | yield fs.unlink(testFile); 28 | console.log("Done!"); 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /monocle-js/03-looping.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('monocle-fs') 3 | , monocle = require('monocle-js') 4 | , sleep = monocle.utils.sleep 5 | , should = require('should') 6 | , testFile = path.resolve("..", "testFile.txt"); 7 | 8 | monocle.run(function*() { 9 | yield fs.writeFile(testFile, "Hello World!"); 10 | var maxNum = 0; 11 | for (var i = 0; i <= maxNum; i++) { 12 | yield fs.appendFile(testFile, " " + i); 13 | } 14 | var data = yield fs.readFile(testFile); 15 | data = data.toString(); 16 | data.should.equal("Hello World! 0 1 2 3 4 5 6 7 8 9 10"); 17 | yield fs.unlink(testFile); 18 | console.log("Done!"); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /monocle-js/04-errors.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('monocle-fs') 3 | , monocle = require('monocle-js') 4 | , o_O = monocle.o_O 5 | , sleep = monocle.utils.sleep 6 | , should = require('should') 7 | , testFile = path.resolve("..", "testFile.txt"); 8 | 9 | var gratuitousFileFunction = o_O(function*() { 10 | yield fs.writeFile(testFile, "Hello World"); 11 | yield sleep(0.5); 12 | yield fs.appendFile(testFile, "!"); 13 | yield fs.appendFile(testFile, " :-)"); 14 | var data = yield fs.readFile(testFile); 15 | data = data.toString(); 16 | data.should.equal("Hello World! :-)"); 17 | yield fs.unlink(testFile); 18 | }); 19 | 20 | monocle.run(function*() { 21 | try { 22 | yield gratuitousFileFunction(); 23 | } catch (e) { 24 | return console.log(e); 25 | } 26 | console.log("Done!"); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /monocle-js/05-kitchen-sink.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('monocle-fs') 3 | , monocle = require('monocle-js') 4 | , o_O = monocle.o_O 5 | , sleep = monocle.utils.sleep 6 | , should = require('should') 7 | , testFile = path.resolve("..", "testFile.txt"); 8 | 9 | var gratuitousFileFunction = o_O(function*() { 10 | try { 11 | yield fs.stat(testFile); 12 | yield fs.unlink(testFile); 13 | } 14 | catch (ignore) {} 15 | yield fs.writeFile(testFile, "Hello World"); 16 | yield sleep(0.5); 17 | var evenness = ''; 18 | if (Date.now() % 2 === 0) { 19 | evenness = 'Time was even, doing nothing! '; 20 | } else { 21 | evenness = 'Time was odd, need to wait a ms...'; 22 | yield sleep(0.001); 23 | } 24 | yield fs.appendFile(testFile, "!"); 25 | yield fs.appendFile(testFile, " " + evenness); 26 | var maxNum = 10; 27 | for (var i = 0; i <= maxNum; i++) { 28 | yield fs.appendFile(testFile, " " + i); 29 | } 30 | var data = yield fs.readFile(testFile); 31 | data = data.toString(); 32 | data.should.equal("Hello World! " + evenness + " 0 1 2 3 4 5 6 7 8 9 10"); 33 | yield fs.unlink(testFile); 34 | }); 35 | 36 | monocle.run(function*() { 37 | try { 38 | yield gratuitousFileFunction(); 39 | } catch (e) { 40 | return console.log(e); 41 | } 42 | console.log("Done!"); 43 | }); 44 | 45 | 46 | -------------------------------------------------------------------------------- /monocle-js/README.md: -------------------------------------------------------------------------------- 1 | Monocle-js 2 | ---------- 3 | 4 | Take a look at [Monocle's README](https://github.com/jlipps/monocle-js) to 5 | understand more about the motivation behind Monocle and all the things you can 6 | do with it. 7 | 8 | Note that to run these examples you'll need at least Node 0.11.3 and to run 9 | with the `--harmony` flag. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-shootout", 3 | "description": "Comparison between various async libraries", 4 | "tags": [ 5 | "javascript", 6 | "async", 7 | "callbacks", 8 | "promises" 9 | ], 10 | "version": "0.0.1", 11 | "author": "jlipps@gmail.com", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/jlipps/async-shootout.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/jlipps/async-shootout/issues" 18 | }, 19 | "engines": [ 20 | "node" 21 | ], 22 | "main": "./lib/main.js", 23 | "directories": { 24 | "lib": "./lib" 25 | }, 26 | "dependencies": { 27 | "should": "~1.2.1", 28 | "monocle-js": "~0.1.8", 29 | "monocle-fs": "~0.0.4", 30 | "bluebird": "~0.11.5-0" 31 | }, 32 | "scripts": {}, 33 | "devDependencies": {} 34 | } 35 | -------------------------------------------------------------------------------- /promises-bluebird-topdown/05-kitchen-sink-es6.js: -------------------------------------------------------------------------------- 1 | //jshint esnext:true 2 | 3 | var Promise = require("bluebird"); 4 | var path = require("path"); 5 | var fs = Promise.promisifyAll(require("fs")); 6 | var should = require('should'); 7 | var testFile = path.resolve("..", "testFile.txt"); 8 | 9 | function doNothing() {} 10 | 11 | 12 | var gratuitousFileFunction = function() { 13 | return ensureFileDoesntExist() 14 | .then(writeData) 15 | .then(verifyData) 16 | .then(removeFile); 17 | } 18 | 19 | function ensureFileDoesntExist() { 20 | return fs.statAsync(testFile).then( 21 | removeFile, 22 | doNothing); 23 | } 24 | 25 | function removeFile() { return fs.unlinkAsync(testFile); } 26 | 27 | function writeData() { 28 | return fs.writeFileAsync(testFile, "Hello World") 29 | .then(_ => Promise.delay(500)) 30 | .then(writeTime) 31 | .then(timeEvenness => 32 | writeNumbers().thenReturn(timeEvenness)) 33 | } 34 | 35 | function writeTime() { 36 | var rightMoment, evenness; 37 | if (Date.now() % 2 === 0) { 38 | evenness = 'Time was even, doing nothing!' 39 | rightMoment = Promise.fulfilled(); 40 | } else { 41 | evenness = 'Time was odd, need to wait a ms...'; 42 | rightMoment = Promise.delay(1); 43 | } 44 | return rightMoment 45 | .then(_ => fs.appendFileAsync(testFile, "!")) 46 | .then(_ => fs.appendFileAsync(testFile, " " + evenness)) 47 | .thenReturn(evenness); 48 | } 49 | 50 | function writeNumbers() { 51 | var cur = Promise.fulfilled(); 52 | for (var i = 0; i <= 10; ++i) { 53 | cur = cur.then(function(i) { 54 | return fs.appendFileAsync(testFile, " " + i); 55 | }.bind(null, i)); 56 | } 57 | return cur; 58 | } 59 | 60 | function verifyData(timeEvenness) { 61 | return fs.readFileAsync(testFile, 'utf8') 62 | .then(data => 63 | data.should.equal("Hello World! " 64 | + timeEvenness 65 | + " 0 1 2 3 4 5 6 7 8 9 10")); 66 | } 67 | 68 | gratuitousFileFunction().then( 69 | _ => console.log("Done!"), 70 | err =>console.log(err)); 71 | 72 | -------------------------------------------------------------------------------- /promises-bluebird-topdown/05-kitchen-sink.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | 8 | var gratuitousFileFunction = function () { 9 | return ensureFileDoesntExist() 10 | .then(writeData) 11 | .then(verifyData) 12 | .then(removeFile); 13 | } 14 | 15 | 16 | function ensureFileDoesntExist() { 17 | return fs.statAsync(testFile) 18 | .then(removeFile, doNothing); 19 | } 20 | 21 | function removeFile() { return fs.unlinkAsync(testFile); } 22 | function doNothing() {} 23 | 24 | function writeData() { 25 | return writeHelloWorldAndWait() 26 | .then(writeTime) 27 | .then(function(timeEvenness) { 28 | return writeNumbers().thenReturn(timeEvenness); 29 | }) 30 | } 31 | 32 | function writeHelloWorldAndWait() { 33 | return fs.writeFileAsync(testFile, "Hello World") 34 | .then(function() { 35 | return Promise.delay(500); 36 | }); 37 | } 38 | 39 | function writeTime() { 40 | var rightMoment, evenness; 41 | if (Date.now() % 2 === 0) { 42 | evenness = 'Time was even, doing nothing!' 43 | rightMoment = Promise.fulfilled(); 44 | } else { 45 | evenness = 'Time was odd, need to wait a ms...'; 46 | rightMoment = Promise.delay(1); 47 | } 48 | return rightMoment.then(function() { 49 | return fs.appendFileAsync(testFile, "!"); 50 | }).then(function() { 51 | return fs.appendFileAsync(testFile, " " + evenness); 52 | }).thenReturn(evenness); 53 | } 54 | 55 | function writeNumbers() { 56 | var cur = Promise.fulfilled(); 57 | for (var i = 0; i <= 10; ++i) { 58 | cur = cur.then(function(i) { 59 | return fs.appendFileAsync(testFile, " " + i); 60 | }.bind(null, i)); 61 | } 62 | return cur; 63 | } 64 | 65 | function verifyData(timeEvenness) { 66 | return fs.readFileAsync(testFile, 'utf8').then(function (data) { 67 | data.should.equal("Hello World! " + timeEvenness + 68 | " 0 1 2 3 4 5 6 7 8 9 10"); 69 | }); 70 | } 71 | 72 | gratuitousFileFunction().then(function () { 73 | console.log("Done!"); 74 | }).catch (function (err) { 75 | console.log(err.stack); 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /promises-bluebird-topdown/README.md: -------------------------------------------------------------------------------- 1 | Promise kitchen-sink with topdown decomposition 2 | =========================== 3 | 4 | This example takes a top-down design approach, adding some readability to the 5 | original example at the expense of a slightly larger file size. 6 | 7 | The top-down approach consists of the following 8 | 9 | 1. Describe an overview of the problem in your head, with words. 10 | 2. Try to write code that is the closest to those words 11 | 3. For every unimplemented thing, repeat until done. 12 | 13 | > This is the example description I started with. Descriptions are not always 14 | > meant to be expressed in text - they can also be expressed in code. However, 15 | > none of the current implementations were clear enough so I decided to start 16 | > with a tiny overview of what the file is supposed to do 17 | 18 | The kitchen sink creates a test file with some contents, then reads the file 19 | and verifies whether its content is as specified. 20 | 21 | 1. It must ensure that the file is a new one 22 | 2. It must put the following content: Hello World, !, a string describing the 23 | evenness of the current time and the numbers 0-10. 24 | 3. It must check whether that content is as expected. 25 | 26 | # benefits 27 | 28 | * This example reads perfectly well from top to bottom, almost like blocking 29 | I/O code. The point of promises is to achieve exactly that. The ES6 variant 30 | is even cleaner and closer, with even less noise. 31 | 32 | Of course, some things are different. Semicolons are replaced with `.then` 33 | chaining. Function application is `pvalue.then(fn)` rather than `fn(value)` 34 | or for multiple arguments `Promise.join(pArg1, pArg2).then(fn)` rathern than 35 | `fn(arg1, arg2)`. try/catch is replaced with `.catch` and errors are 36 | automatically bubbled until the appropriate catch handler is found. 37 | 38 | However, enough things are similar that you can write code without 39 | reinventing the entire language. You can return values from functions again. 40 | You can even use for loops to sequentially iterate over a list of numbers. 41 | You can use `Array.map` and `Array.reduce`. You can treat promises like 42 | variables in that they can remember the value and it can be accessed at any 43 | time. 44 | 45 | This resuls with very clean, straightforward looking code that reads almost 46 | like plain English. 47 | 48 | * Bluebird is the only external library used. Regarding performance, bluebird 49 | is practically as fast as callbacks. [This blog post][switch-bluebird] 50 | contains the benchmarks. 51 | 52 | Debugging promise code with Bluebird is simply awesome. Just run 53 | 54 | BLUEBIRD_DEBUG=1 node 05-kitchen-sink.js 55 | 56 | to get long stack traces across multiple events. Bluebird's flavor of 57 | LST is optimized very well - the overhead of long stack traces support is 58 | [barely noticable in most I/O situations][benchmark-lsp] 59 | 60 | * Since the example isn't by its nature generic code (yet), there is no need to 61 | make a package.json. Writing a nice API and a npm module is orthogonal to 62 | proper code decomposition, which is orthogonal to using promises. You can 63 | have it all :D 64 | 65 | [switch-bluebird]: http://spion.github.io/posts/why-i-am-switching-to-promises.html 66 | [benchmark-lsp]: https://gist.github.com/spion/6990910 67 | 68 | -------------------------------------------------------------------------------- /promises-bluebird/01-sync-chain.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | function delay(ms) { 8 | return new Promise(function (resolve) { 9 | setTimeout(resolve, ms); 10 | }); 11 | } 12 | 13 | fs.writeFileAsync(testFile, "Hello World").then(function () { 14 | return delay(500); 15 | }).then(function () { 16 | return fs.appendFileAsync(testFile, "!"); 17 | }).then(function () { 18 | return fs.appendFileAsync(testFile, " :-)"); 19 | }).then(function () { 20 | return fs.readFileAsync(testFile); 21 | }).then(function (data) { 22 | data = data.toString() 23 | data.should.equal("Hello World! :-)"); 24 | return fs.unlinkAsync(testFile); 25 | }).then(function () { 26 | console.log("Done!"); 27 | }); -------------------------------------------------------------------------------- /promises-bluebird/02-conditionals.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | function delay(ms) { 8 | return new Promise(function (resolve) { 9 | setTimeout(resolve, ms); 10 | }); 11 | } 12 | 13 | fs.statAsync(testFile).then(function () { 14 | return fs.unlinkAsync(testFile); 15 | }).finally(function () { //Either gets here immediately from the error, or waits for the unlink 16 | var evenness; 17 | fs.writeFileAsync(testFile, "Hello World").then(function () { 18 | return delay(500); 19 | }).then(function () { 20 | if (Date.now() % 2 === 0) { 21 | evenness = 'Time was even, doing nothing! '; 22 | } else { 23 | evenness = 'Time was odd, need to wait a ms...'; 24 | return delay(1); 25 | } 26 | }).then(function () { 27 | return fs.appendFileAsync(testFile, "!"); 28 | }).then(function () { 29 | return fs.appendFileAsync(testFile, " " + evenness); 30 | }).then(function () { 31 | return fs.readFileAsync(testFile); 32 | }).then(function (data) { 33 | data = data.toString(); 34 | data.should.equal("Hello World! " + evenness); 35 | return fs.unlinkAsync(testFile); 36 | }).then(function () { 37 | console.log("Done!"); 38 | }); 39 | }); -------------------------------------------------------------------------------- /promises-bluebird/03-looping.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | fs.writeFileAsync(testFile, "Hello World!").then(function() { 8 | var cur = Promise.fulfilled(); 9 | for(var i = 0; i <= 10; ++i) { 10 | cur = cur.then(function(i) { 11 | return fs.appendFileAsync(testFile, " " + i); 12 | }.bind(null, i)); 13 | } 14 | return cur; 15 | }).then(function(){ 16 | return fs.readFileAsync(testFile); 17 | }).then(function(data) { 18 | data = data.toString(); 19 | data.should.equal("Hello World! 0 1 2 3 4 5 6 7 8 9 10"); 20 | return fs.unlinkAsync(testFile); 21 | }).then(function(){ 22 | console.log("Done!"); 23 | }); 24 | -------------------------------------------------------------------------------- /promises-bluebird/04-errors.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | function delay(ms) { 8 | return new Promise(function (resolve) { 9 | setTimeout(resolve, ms); 10 | }); 11 | } 12 | 13 | var gratuitousFileFunction = function () { 14 | return fs.writeFileAsync(testFile, "Hello World").then(function () { 15 | return delay(500); 16 | }).then(function () { 17 | return fs.appendFileAsync(testFile, "!"); 18 | }).then(function () { 19 | return fs.appendFileAsync(testFile, " :-)"); 20 | }).then(function () { 21 | return fs.readFileAsync(testFile); 22 | }).then(function (data) { 23 | data = data.toString(); 24 | data.should.equal("Hello World! :-)"); 25 | return fs.unlinkAsync(testFile); 26 | }); 27 | }; 28 | 29 | gratuitousFileFunction().then(function () { 30 | console.log("Done!"); 31 | }). 32 | catch (function (e) { 33 | console.log(e); 34 | }); -------------------------------------------------------------------------------- /promises-bluebird/05-kitchen-sink.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"); 2 | var path = require("path"); 3 | var fs = Promise.promisifyAll(require("fs")); 4 | var should = require('should'); 5 | var testFile = path.resolve("..", "testFile.txt"); 6 | 7 | function delay(ms) { 8 | return new Promise(function (resolve) { 9 | setTimeout(resolve, ms); 10 | }); 11 | } 12 | var gratuitousFileFunction = function () { 13 | return fs.statAsync(testFile).then(function () { 14 | return fs.unlinkAsync(testFile); 15 | }).then(next, next); 16 | 17 | function next() { 18 | var evenness; 19 | return fs.writeFileAsync(testFile, "Hello World").then(function () { 20 | return delay(500); 21 | }).then(function () { 22 | if (Date.now() % 2 === 0) { 23 | evenness = 'Time was even, doing nothing! '; 24 | } else { 25 | evenness = 'Time was odd, need to wait a ms...'; 26 | return delay(1); 27 | } 28 | }).then(function () { 29 | return fs.appendFileAsync(testFile, "!"); 30 | }).then(function () { 31 | return fs.appendFileAsync(testFile, " " + evenness); 32 | }).then(function () { 33 | var cur = Promise.fulfilled(); 34 | for (var i = 0; i <= 10; ++i) { 35 | cur = cur.then(function(i) { 36 | return fs.appendFileAsync(testFile, " " + i); 37 | }.bind(null, i)); 38 | } 39 | return cur; 40 | }).then(function (v) { 41 | return fs.readFileAsync(testFile); 42 | }).then(function (data) { 43 | data = data.toString(); 44 | data.should.equal("Hello World! " + evenness + 45 | " 0 1 2 3 4 5 6 7 8 9 10"); 46 | return fs.unlinkAsync(testFile); 47 | }); 48 | } 49 | }; 50 | 51 | gratuitousFileFunction().then(function () { 52 | console.log("Done!"); 53 | }).catch (function (err) { 54 | console.log(err); 55 | }); 56 | -------------------------------------------------------------------------------- /promises-bluebird/README.md: -------------------------------------------------------------------------------- 1 | Promise Reference Scripts 2 | =========================== 3 | 4 | These javascript files show how promises are used in Node to control asynchronous function execution. 5 | 6 | Other projects should use these files as a reference in order to create their own examples showcasing their approach. 7 | --------------------------------------------------------------------------------