Skip to content

Instantly share code, notes, and snippets.

@balupton
Last active March 22, 2016 10:50
Show Gist options
  • Save balupton/4721905fe5d51c541660 to your computer and use it in GitHub Desktop.
Save balupton/4721905fe5d51c541660 to your computer and use it in GitHub Desktop.
Node Failsafe Workshop

Node Failsafe Workshop

Short URL: http://bevry.me/nodefailsafe

Details: https://ti.to/hackerretreat/nodefailsafe

Wifi: Microsoft-Public

Help yourself to the drinks!

Ask for the toilet, as it is in a secure room!

Getting Started

  1. Open up the following about error handling in the background:

  2. Open up the following about debugging in the background:

  3. The mentor(s) will issue you challenges individually to develop your skills

  4. If you get stuck on the challenges, need help, have questions, or are finished, let the mentor know and they'll happily help

Debugging

You'll need this later on:

Install TraceGL:

git clone https://github.com/traceglMPL/tracegl.git ~/tracegl-repo
cd ~/tracegl-repo
node tools/packer.js trace/trace_server tracegl.js
cp tracegl.js ~/

Install Node Inspector:

npm i -g node-inspector

Verification

Install Learn You Node:

npm i -g learnyounode

Very your exercise:

learnyounode select "Juggling Async"
learnyounode run 01-app.js
learnyounode verify 01-app.js

Exercise 01: Requests

This exercise builds upon learnyounode/juggling_async (exercise 09).

Output the result of 3 URLs (given to you as command line arguments) in the order they are specified.

Do not use a flow control library at this stage.

If an error occurs, ignore it. We will cover error handling in a later exercise.

Verify your result using:

learnyounode select "Juggling Async"
learnyounode run 01-app.js
learnyounode verify 01-app.js

HINTS

Servers may output data in chunks, meaning that there could be multiple data events emitted. For each request, you will need to concatenate the result of data event into a data variable that is then used for that request.

As you will be handling 3 requests, you will want to move your "fetching" logic into it's own function, that is then called for each request.

Remember from learnyounode:

  • Command line arguments are provided within the process.argv array
  • You can use http.get to get the data of a URL, more documentation is available on http.request if you get stuck

For now you can expect each request to work successfully, enabling you to safely ignore errors. Later, this will not be the case, but do not worry about it now, we will build on error handling later.

For now, do not use a flow control library, just call our created "fetch" function multiple times using "callback hell" style code. We'll build on this later.


Exercise 02: Module

Build upon the last lesson by abstracting out the fetch function into its own module.

Do not use a flow control library at this stage.

If an error occurs, ignore it. We will cover error handling in a later exercise.


HINTS

If you are new to modules, check out learnyouode/make_it_modular (lesson 06).

Remember, node conventions dictate that callbacks are to have the first argument to be the error (or null if no error), with subsequent arguments being the results. E.g. next(null, data).


Exercise 03: Throw Error

Build upon the last lesson by providing our callback with the error that occurs, and throw it in your application code.

Sometimes things go wrong. One of the three requests may fail. Crash the application by throwing the error.

Do not use a flow control library at this stage.


HINTS

Calling http.get (like http.request)) returns a http.ClientRequest instance. http.ClientRequest is a class that inherits from the events.EventEmitter class.

Event Emitters in Node.js are objects (or rather class instances) that can have listeners bound and emitted on the instance. This means we can do person.on('thirsty', drinkWaterFunction) to listen to the thirsty event on our person instance, and drink some water with a function we defined elsewhere.

Event Emitters are important, as they are a common paradigm for accomplishing background tasks in Node.js.

So far, the code we've written has been very procedural. A very "do this, then this, then this" style. However, events emtiters, allow us to do a "listen for this, and when it eventually happens, then do this". In fact, we've already used an event emitter when listening to the response argument provided by the http.get callback, which is an instance of the http.ClientResponse class. It's an event emitter that exposes the data and end events, because it is a stream.

However, event emitters also play another important role. They are great for emitting background errors. That is, errors that occur outside our normal procedural synchronous flow. In the case here, if the request fails, that could happen at any point in our application, therefore there is no clear place to emit it. Instead, we should emit an error event on the http.ClientRequest instance.

Node does something great for us, where if an event emitter emits an error event, that is not listened to, the application will throw the error. This ensures that we are alerted of uncaught exceptions and they do not slip through the cracks. This forces us to deal with errors correctly.

At this point, we just want our fetch module to return any emitted error to our completion callback if it occurs. Then have our app module, throw the error if it was given to us. It's not the best way of doing things, as it still causes our application to crash, but we will build on this concept later.


Exercise 04: Duplicate Complete

Build upon the last example by preventing our completion callback from firing twice.

In the case that our url completes, then errors, we do not want our completion callback to execute twice, causing an unexpected state, and repeated logic flows.

Do not use a flow control library at this stage.


HINTS

As we are still not using a flow control library at this stage, using a boolean flag in our fetch logic of whether or not we have completed yet will be the easiest way of accomplishing this.


Exercise 05: Readstream

Build upon the last example by abstracting out our stream reading logic into its own module.

Do not use a flow control library at this stage.

Remember to prevent duplicate completions in your updated fetch code.


HINTS

No hints this time. You got this.


Exercise 06: Reality

Build upon the last example by dealing with reality; move the completion checks into the caller code.

Do not use a flow control library at this stage.


HINTS

Unfortunately, not everyone is trained in writing beautiful correct code like you. It's often the case that third party code we use, may fail at random times, throw errors, or even call the completion callback multiple times.

And even if it isn't often the case, we can't risk having our application enter an invalid state in production. So we must be diligent, always.

In this exercise, we will use the fetch code from exercise 03-throw-error (before we added the completion checks in 04-duplicate-completion), and instead add the completion checks to our calling code in our app.js file.

Do not worry about uncaught exceptions at this stage. We will cover that later.


Exercise 07: Fetch All

Build upon the last example by abstracting out the fetching of the three URLs into a generic fetchall module.

The fetchall module should accept a signature of (urls, next), where urls is an array of the urls we want to fetch the data for, and next is the completion callback accepting the signature (err, results), where err is a possible error, and results is an array of the result data of each url, in the order the URLs were given (the order is important, be sure to remember this).

To make our lives easier, the following is recommended:

  1. URLs should be fetched in parallel (all at once), rather than serially (one after the other)
  2. Errors when fetching a URL should be thrown at this stage (we will build on this later)
  3. Do not worry about the use case of receiving zero URLs at this stage (we will build on it later)

Do not use a flow control library at this stage.

Be sure to check for multiple completion events of our fetchall module when using it in our app module.


HINTS

A lot is asked of you in this step, however the changes actually simplify our code thus far, but may be a bit difficult to grasp. Here are a few hints.

You will need to keep track of which URLs have completed, and which have not. You should throw an error if fetching a URL has emitted the completion callback more than once.

You will need to keep track of the results of each URLs, and make sure you return the results as an array in the order they were received.

You will need to keep track of how many URLs have completed, and if the completion count is the same as the amount of URLs we were given, then it is time to call the completion callback.

The easiest way of accomplishing this is to maintain a count variable for the amount of completed URL fetches, as well as a hash object containing the fetched result of our URLs.

To get the result array in the same order as we were provided the URLs, we could create a new results array, then cycle through our received URLs, and add the result data (from our hash object), to the array; recreating the order we received them. We could even use Array.prototype.map to simplify this cycling.

If this is over your head, ask a mentor or try whatever way you think is best. It's okay to make mistakes, they provide a lesson of what not to do, which is equally important in the grand scheme of things.


BONUS POINTS

Once you've finished the exercise, there are a few bonus points you can get.

There are several interesting things going on in this exercise, can you answer the following?

  1. Why would throwing an error in our fetchall module cause an unexpected state?

  2. What would happen if our fetchall module received zero URLs?

  3. Why do we still need to check for multiple completions of our fetchall module when calling it in our app module?

Once you've tried answered those questions, validate your assumptions by comparing them with the best practices provided by the Error Handling in Node.js guide issued by Joyent.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment