Skip to content

Commit b2f1d7b

Browse files
committed
Light pass over chapter 11
1 parent 0838bee commit b2f1d7b

File tree

8 files changed

+205
-188
lines changed

8 files changed

+205
-188
lines changed

11_async.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Both prominent JavaScript programming platforms—((browser))s and ((Node.js))
5959

6060
{{indexsee [function, callback], "callback function"}}
6161

62-
One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. Such a function starts some process, sets things up so that the callback function is called when the process finishes, and then returns.
62+
One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous a function starts some process, sets things up so that the callback function is called when the process finishes, and then returns.
6363

6464
{{index "setTimeout function", waiting}}
6565

@@ -69,7 +69,7 @@ As an example, the `setTimeout` function, available both in Node.js and in brows
6969
setTimeout(() => console.log("Tick"), 500);
7070
```
7171

72-
Waiting is not generally a very important type of work, but it can be very useful when updating an animation or checking whether some other action is taking longer than expected.
72+
Waiting is not generally a very important type of work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some other action is taking longer than expected.
7373

7474
{{index "readTextFile function"}}
7575

@@ -159,7 +159,7 @@ The function returns the result of this chain of `then` calls. The initial promi
159159

160160
In this code, the functions used in the first two `then` calls return a regular value, which will immediately be passed into the promise returned by `then` when the function returns. The last one returns a promise (`textFile(filename)`), making it an actual asynchronous step.
161161

162-
In this case, it would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result.
162+
Tt would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result.
163163

164164
```
165165
function jsonFile(filename) {
@@ -212,7 +212,7 @@ A function passed to the `Promise` constructor receives a second argument, along
212212

213213
{{index "textFile function"}}
214214

215-
The `readTextFile` function also uses the convention of passing the error as a second argument, if there is a problem. Our `textFile` wrapper should actually look at that argument, so that a failure causes the promise it returns to be rejected.
215+
When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually look at that argument, so that a failure causes the promise it returns to be rejected.
216216

217217
```{includeCode: true}
218218
function textFile(filename) {
@@ -239,6 +239,8 @@ new Promise((_, reject) => reject(new Error("Fail")))
239239
// → Handler 2: nothing
240240
```
241241

242+
The first regular handler function isn't called, because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second handler function.
243+
242244
{{index "uncaught exception", "exception handling"}}
243245

244246
Much like an uncaught exception is handled by the environment, JavaScript environments can detect when a promise rejection isn't handled and will report this as an error.
@@ -253,11 +255,11 @@ One of the crows stands out—a large scruffy female with a few white feathers i
253255

254256
Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight towards the roof of the hangar building, disappearing into an air vent.
255257

256-
Inside the building, in a narrow space under the roof of an unfinished stairwell, you can hear an odd tapping sound—soft, but persistent. The crow is sitting there, surrounded by her stolen snack, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing.
258+
Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snack, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing.
257259

258-
This crow is known to her peers as “cāāw-krö“, but since that is hard for us to remember, we'll call her Carla.
260+
This crow is known to her peers as “cāāw-krö”. But since those sounds are poorly suited for human vocal chords, we'll refer to her as Carla.
259261

260-
Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she could understand roughly what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of JavaScript code.
262+
Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of JavaScript code.
261263

262264
## Breaking In
263265

@@ -280,7 +282,7 @@ function withTimeout(promise, time) {
280282
}
281283
```
282284

283-
This makes use of the fact that a promise can only be resolved or rejected once—if the given promise resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored.
285+
This makes use of the fact that a promise can only be resolved or rejected once—if the promise given as argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored.
284286

285287
To find the whole passcode, we need to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong, and must try the next digit. If the request times out, we have found another correct digit, and must continue by adding another digit.
286288

@@ -323,7 +325,7 @@ Even with promises, this kind of asynchronous code is annoying to write. Promise
323325

324326
{{index "synchronous programming", "asynchronous programming"}}
325327

326-
The thing the cracking function actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, this'd be simpler to express.
328+
The thing the cracking function actually does is completely linear—it always waits for the previous action to complete before starting the next one. In a synchronous programming model, it'd be more straightforward to express.
327329

328330
{{index "async function", "await keyword"}}
329331

@@ -494,6 +496,8 @@ function displayFrame(frame) {
494496
This maps over the images in `frame` (which is an array of display data arrays) to create an array
495497
of request promises. It then returns a promise that combines all of those.
496498

499+
In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that only resolves when the playback is stopped again via the `stop` method.
500+
497501
```{includeCode: true}
498502
function wait(time) {
499503
return new Promise(accept => setTimeout(accept, time));
@@ -523,8 +527,6 @@ class VideoPlayer {
523527

524528
The `wait` function wraps `setTimeout` in a promise that resolves after the given amount of milliseconds. This is useful for controlling the speed of the playback.
525529

526-
In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that only resolves when the playback is stopped again via the `stop` method.
527-
528530
```
529531
let video = new VideoPlayer(clipImages, 100);
530532
video.play().catch(e => {
@@ -533,6 +535,8 @@ video.play().catch(e => {
533535
setTimeout(() => video.stop(), 15000);
534536
```
535537

538+
For the entire week that the screen wall stands, every evening, when it is dark, a huge glowing orange bird mysteriously appears on it.
539+
536540
## The event loop
537541

538542
{{index "asynchronous programming", scheduling, "event loop", timeline}}
@@ -611,11 +615,11 @@ async function fileSizes(files) {
611615

612616
{{index "async function"}}
613617

614-
The `async name =>` part shows how ((arrow function))s can also be made `async` by putting the word `async` in front of them.
618+
The `async fileName =>` part shows how ((arrow function))s can also be made `async` by putting the word `async` in front of them.
615619

616620
{{index "Promise.all function"}}
617621

618-
The code doesn't immediately look suspicious...it maps the `async` arrow function over the array of names, creating an array of promises, and then uses `Promise.all` to wait for all of these before returning the list they build up.
622+
The code doesn't immediately look suspicious... it maps the `async` arrow function over the array of names, creating an array of promises, and then uses `Promise.all` to wait for all of these before returning the list they build up.
619623

620624
But it is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read.
621625

@@ -672,7 +676,7 @@ There's a security camera near Carla's lab that's activated by a motion sensor.
672676

673677
{{index "Date class", "Date.now function", timestamp}}
674678

675-
She's also been logging the times at which the camera is tripped for a while, and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()` per line.
679+
She's also been logging the times at which the camera is tripped for a while, and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line.
676680

677681
```{lang: null}
678682
1695709940692

21_skillsharing.md

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,18 @@ export class Router {
193193
add(method, url, handler) {
194194
this.routes.push({method, url, handler});
195195
}
196-
resolve(context, request) {
197-
let path = parse(request.url).pathname;
198-
199-
for (let {method, url, handler} of this.routes) {
200-
let match = url.exec(path);
201-
if (!match || request.method != method) continue;
202-
let urlParts = match.slice(1).map(decodeURIComponent);
203-
return handler(context, ...urlParts, request);
204-
}
205-
return null;
196+
}
197+
198+
async function resolveRequest(router, context, request) {
199+
let path = parse(request.url).pathname;
200+
201+
for (let {method, url, handler} of this.routes) {
202+
let match = url.exec(path);
203+
if (!match || request.method != method) continue;
204+
let urlParts = match.slice(1).map(decodeURIComponent);
205+
let response =
206+
await handler(context, ...urlParts, request);
207+
if (response) return response;
206208
}
207209
}
208210
```
@@ -223,40 +225,30 @@ The handler functions are called with the `context` value (which will be the ser
223225

224226
When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. So let's use a solid, well-tested ((static file)) server from ((NPM)) instead.
225227

226-
{{index "createServer function", "ecstatic package"}}
228+
{{index "createServer function", "serve-static package"}}
227229

228-
I opted for `ecstatic`. This isn't the only such server on NPM, but it works well and fits our purposes. The `ecstatic` package exports a function that can be called with a configuration object to produce a request handler function. We use the `root` option to tell the server where it should look for files. The handler function accepts `request` and `response` parameters and can be passed directly to `createServer` to create a server that serves _only_ files. We want to first check for requests that we should handle specially, though, so we wrap it in another function.
230+
I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function.
229231

230232
```{includeCode: ">code/skillsharing/skillsharing_server.mjs"}
231233
import {createServer} from "node:http";
232-
import ecstatic from "ecstatic";
233-
import {Router} from "./router.mjs";
234+
import serveStatic from "serve-static";
234235
235-
const router = new Router();
236-
const defaultHeaders = {"Content-Type": "text/plain"};
236+
function notFound(request, response) {
237+
response.writeHead(404, "Not found");
238+
response.end("<h1>Not found</h1>");
239+
}
237240
238241
class SkillShareServer {
239242
constructor(talks) {
240243
this.talks = talks;
241244
this.version = 0;
242245
this.waiting = [];
243246
244-
let fileServer = ecstatic({root: "./public"});
247+
let fileServer = serveStatic("./public");
245248
this.server = createServer((request, response) => {
246-
let resolved = router.resolve(this, request);
247-
if (resolved) {
248-
resolved.catch(error => {
249-
if (error.status != null) return error;
250-
return {body: String(error), status: 500};
251-
}).then(({body,
252-
status = 200,
253-
headers = defaultHeaders}) => {
254-
response.writeHead(status, headers);
255-
response.end(body);
256-
});
257-
} else {
258-
fileServer(request, response);
259-
}
249+
serveFromRouter(this, request, response, () => {
250+
fileServer(request, response, notFound);
251+
});
260252
});
261253
}
262254
start(port) {
@@ -268,7 +260,26 @@ class SkillShareServer {
268260
}
269261
```
270262

271-
This uses a similar convention as the file server from the [previous chapter](node) for responses—handlers return promises that resolve to objects describing the response. It wraps the server in an object that also holds its state.
263+
The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. This allows us to “chain” several request handlers, allowing each to either handle the request, or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error.
264+
265+
Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handler in the router return promises that resolve to objects describing the response.
266+
267+
```{includeCode: ">code/skillsharing/skillsharing_server.mjs"}
268+
import {Router} from "./router.mjs";
269+
270+
const router = new Router();
271+
const defaultHeaders = {"Content-Type": "text/plain"};
272+
273+
async function serveResponse(value, response) {
274+
let {body, status = 200, headers = defaultHeaders} =
275+
await resolved.catch(error => {
276+
if (error.status != null) return error;
277+
return {body: String(error), status: 500};
278+
});
279+
response.writeHead(status, headers);
280+
response.end(body);
281+
}
282+
```
272283

273284
### Talks as resources
274285

@@ -479,7 +490,7 @@ The ((client))-side part of the skill-sharing website consists of three files: a
479490

480491
{{index "index.html"}}
481492

482-
It is a widely used convention for web servers to try to serve a file named `index.html` when a request is made directly to a path that corresponds to a directory. The ((file server)) module we use, `ecstatic`, supports this convention. When a request is made to the path `/`, the server looks for the file `./public/index.html` (`./public` being the root we gave it) and returns that file if found.
493+
It is a widely used convention for web servers to try to serve a file named `index.html` when a request is made directly to a path that corresponds to a directory. The ((file server)) module we use, `serve-static`, supports this convention. When a request is made to the path `/`, the server looks for the file `./public/index.html` (`./public` being the root we gave it) and returns that file if found.
483494

484495
Thus, if we want a page to show up when a browser is pointed at our server, we should put it in `public/index.html`. This is our index file:
485496

0 commit comments

Comments
 (0)