├── .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 | [](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 |
--------------------------------------------------------------------------------