Skip to content

Commit 3b075af

Browse files
committed
updated async to not use overly complicated file system example
1 parent 14ef04e commit 3b075af

File tree

1 file changed

+75
-126
lines changed

1 file changed

+75
-126
lines changed

_posts/2012-11-5-async.md

Lines changed: 75 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -11,103 +11,71 @@ tags:
1111

1212
First, let's see the problem we're trying to avoid:
1313

14-
Let's start by creating a few files in a brand new directory. Use the name of the files below and the contents as described for this example. The numeric names of the file are used to help clarify the point being made.
15-
16-
`1.html`
17-
{% highlight html %}
18-
<span class="Speaker">Joe Andaverde</span>
19-
<p class="bio">Joe is a Software Engineer for Softek Solutions, Inc. He's passionate about sharing his knowledge with others.</p>
20-
{% endhighlight %}
21-
22-
`2.html`
23-
{% highlight html %}
24-
<span class="Speaker">Dusty Burwell</span>
25-
<p class="bio">Dusty is a dude.</p>
26-
{% endhighlight %}
27-
2814
{% highlight javascript %}
29-
var fs = require('fs');
30-
31-
for (var i = 1; i <= 2; i++) {
32-
fs.readFile(i + ".html", function (err, data) {
33-
if (err) return;
34-
35-
var matches = data.toString().match(/<span class=\"Speaker\">([\s\S]+)<\/span>[\s\S]+<p class=\"bio\">([\s\S]+)<\/p>/i);
36-
37-
if (matches) {
38-
var name = matches[1];
39-
var bio = matches[2];
40-
fs.writeFile(i + '.json', JSON.stringify({name: name, bio: bio}, null, 2));
41-
}
42-
});
15+
for (var i = 0; i < 100; i++) {
16+
setTimeout(function () {
17+
console.log(i);
18+
}, 100);
4319
}
20+
console.log('You might think this gets printed last.')
4421
{% endhighlight %}
4522

46-
Go back to your command window and list files in your current working directory. Notice that the only file created was `3.json`. This is because the callback from read file doesn't actually get executed until the loop has completed. Any other statements after the for loop would also be executed before the callback. This is, in part, because Node is single threaded. The event loop hasn't had a chance to invoke your callback. In any case, all of the callbacks are being fired and overwriting `3.json`.
23+
Go back to your command window and list files in your current working directory. Notice that the only file created was `3.json`. This is because the callback for setTimeout doesn't actually get executed until the for loop has completed. Any other statements after the for loop would also be executed before the callback. This is, in part, because Node is single threaded. The event loop hasn't had a chance to invoke your callback. In any case, all of the callbacks are being fired with the same value of `i`.
4724

4825
How do we avoid this? The important concept here is how scope is handled in JavaScript. Unlike other languages that use block level scope, JavaScript's scope is at a function level. We can get around this problem by introducing a new scope in each iteration of the loop to capture the value of `i` at that moment:
4926

5027
{% highlight javascript %}
51-
var fs = require('fs');
52-
53-
for (var i = 1; i <= 2; i++) {
54-
(function (i) { //Create a new scope that takes a single argument
55-
fs.readFile(i + ".html", function (err, data) {
56-
if (err) return;
57-
58-
var matches = data.toString().match(/<span class=\"Speaker\">([\s\S]+)<\/span>[\s\S]+<p class=\"bio\">([\s\S]+)<\/p>/i);
59-
60-
if (matches) {
61-
var name = matches[1];
62-
var bio = matches[2];
63-
fs.writeFile(i + '.json', JSON.stringify({name: name, bio: bio}, null, 2));
64-
}
65-
});
66-
})(i);//Execute this function with the current value of i
28+
for (var i = 0; i < 100; i++) {
29+
(function (i) {
30+
setTimeout(function () {
31+
console.log(i);
32+
}, 100);
33+
})(i);
6734
}
35+
console.log('this still gets printed first.')
6836
{% endhighlight %}
6937

70-
The lesson to be learned here is that new scopes are your best friend when performing asynchronous actions in JavaScript.
38+
The lesson to be learned here is that new scopes are your best friend when performing asynchronous actions in JavaScript. A better way to do this would be to avoid creating a new function with each iteration of the loop like so:
39+
40+
{% highlight javascript %}
41+
var printNumberLater = function (i) {
42+
setTimeout(function () {
43+
console.log(i);
44+
}, 100);
45+
};
46+
47+
for (var i = 0; i < 100; i++) {
48+
printNumberLater(i);
49+
}
50+
51+
console.log('this still gets printed first.')
52+
{% endhighlight %}
7153

7254
## Asynchronous calls in parallel and then joining them
7355

56+
So how do we go about printing out a something after the loops work has been done.
57+
7458
More often than not there's a need to invoke a set of tasks asynchronously and then start a new set of tasks after completion. There are a variety of libraries that help with performing this situation and cleaning up the code a lot.
7559

7660
{% highlight javascript %}
77-
var fs = require('fs');
78-
var bios = [];
79-
var biosParsed = 0;
80-
var biosToParse = ["1.html", "2.html"];
81-
82-
83-
var saveBios = function (bios) {
84-
for (var i = 0; i < bios.length; i++) {
85-
fs.writeFile(bios[i].name + ".txt", JSON.stringify(bios[i]));
86-
}
61+
var printNumberLater = function (i, cb) {
62+
setTimeout(function () {
63+
console.log(i);
64+
cb();
65+
}, 100);
8766
};
8867

89-
for (var i = 0; i < biosToParse.length; i++) {
90-
(function (fileName) {
91-
fs.readFile(fileName, function (err, data) {
68+
var totalNumbersToPrint = 100;
69+
var finishedCount = 0;
9270

93-
var matches = data.toString().match(/<span class=\"Speaker\">([\s\S]+)<\/span>[\s\S]+<p class=\"bio\">([\s\S]+)<\/p>/i);
94-
95-
if (matches) {
96-
bios.push({
97-
name: matches[1],
98-
bio: matches[2]
99-
});
100-
}
101-
102-
biosParsed++;
103-
104-
//Have all pending callbacks been invoked?
105-
if (biosParsed == biosToParse.length) {
106-
//Save bios now that all async actions have completed
107-
saveBios(bios);
108-
}
109-
});
110-
})(biosToParse[i]);
71+
for (var i = 0; i < totalNumbersToPrint; i++) {
72+
printNumberLater(i, function () {
73+
finishedCount++;
74+
75+
if (finishedCount === totalNumbersToPrint) {
76+
console.log('this finally gets printed after all work is done!');
77+
}
78+
});
11179
}
11280
{% endhighlight %}
11381

@@ -120,37 +88,24 @@ npm install async
12088
{% endhighlight %}
12189

12290
{% highlight javascript %}
123-
var fs = require('fs');
12491
var async = require('async');
125-
var biosToParse = ["1.html", "2.html"];
126-
127-
var saveBios = function (bios) {
128-
for (var i = 0; i < bios.length; i++) {
129-
fs.writeFile(bios[i].name + ".txt", JSON.stringify(bios[i]));
130-
}
92+
var workToBeDone = [];
93+
94+
var printNumberLater = function (i) {
95+
return function (callback) {
96+
setTimeout(function () {
97+
console.log(i);
98+
cb();
99+
}, 100);
100+
};
131101
};
132102

133-
var parseOperations = [];
134-
135-
for (var i = 0; i < biosToParse.length; i++) {
136-
parseOperations.push((function (fileName) {
137-
return function (callback) {
138-
fs.readFile(fileName, function (err, data) {
139-
var matches = data.toString().match(/<span class=\"Speaker\">([\s\S]+)<\/span>[\s\S]+<p class=\"bio\">([\s\S]+)<\/p>/i);
140-
141-
//The first argument by convention is the error
142-
//The second argument is aggregated with all other async results
143-
callback(null, {
144-
name: matches[1],
145-
bio: matches[2]
146-
});
147-
});
148-
};
149-
})(biosToParse[i]));
103+
for (var i = 0; i < 100; i++) {
104+
workToBeDone.push(printNumberLater(i));
150105
}
151106

152-
async.parallel(parseOperations, function (err, results) {
153-
saveBios(results)
107+
async.parallel(workToBeDone, function () {
108+
console.log('this still gets printed last');
154109
});
155110
{% endhighlight %}
156111

@@ -159,37 +114,31 @@ Imagine the above where you had multiple things needing to happen in parallel. R
159114
## Promises
160115

161116
{% highlight javascript %}
162-
var fs = require('fs');
163-
var p = require("promise-extended"),
164-
Promise = p.Promise;
165-
166-
var files = ["0.html", "1.html"];
117+
var p = require("promise-extended");
118+
var Promise = p.Promise;
167119

168120
var operations = [];
169121

170-
for (var i = 0; i < files.length; i++)
122+
var printNumberLater = function (i) {
123+
var p = new Promise();
124+
125+
setTimeout(function () {
126+
console.log(i);
127+
p.callback();
128+
}, 100);
129+
130+
return p; //Return the newly created promise right away.
131+
};
132+
133+
for (var i = 0; i < 100; i++)
171134
{
172-
(function (fileName) {
173-
var p = new Promise();
174-
fs.readFile(fileName, function (err, data) {
175-
var matches = data.toString().match(/<span class=\"Speaker\">([\s\S]+)<\/span>[\s\S]+<p class=\"bio\">([\s\S]+)<\/p>/i);
176-
177-
p.callback({
178-
name: matches[1],
179-
bio: matches[2]
180-
});
181-
});
182-
183-
operations.push(p.promise());
184-
})(files[i]);
135+
var p = printNumberLater(i);
136+
operations.push(p.promise());
185137
}
186138

187139
p.when(operations).then(function (bios) {
188-
for (var i = 0; i < bios.length; i++) {
189-
fs.writeFile(bios[i].name + ".txt", JSON.stringify(bios[i]));
190-
}
140+
console.log('executed after all operations complete.');
191141
});
192-
193142
{% endhighlight %}
194143

195144
### What are they?

0 commit comments

Comments
 (0)