HTTP

  1. Match the header fields of HTTP with a bank of definitions.
  2. Matching HTTP verbs (GET, PUT, PATCH, POST, DELETE) to their common uses.
  3. Match common HTTP status codes (200, 302, 400, 401, 402, 403, 404, 500) to their meanings.
  4. Send a simple HTTP request to google.com
  5. Write a very simple HTTP server using ‘http’ in node with paths that will result in the common HTTP status codes.
const http = require('http');

http.createServer(function(request, response) {
    if (request.url === '/200') {
        response.writeHead(200, { 'Content-Type': 'text/html' });
        response.write('<h1>Hello, world! Status 200 OK!</h1>');
    } else if (request.url === '/403') {
        response.writeHead(403, { 'Content-Type': 'text/html' });
        response.write('<h1>This is Forbidden! Status 403 Forbidden!</h1>');
    } else {
        response.writeHead(404, { 'Content-Type': 'text/html' });
        response.write('<h1>What is that? Status 404 Not Found!</h1>');
    }
    response.end();
}).listen(8080, function() {
    console.log('Listening for requests on port 8080...');
})

Promises

Three states of a Promise - Pending - Fulfilled - Rejected

  1. Instantiate a Promise object
function pause(numberOfSeconds) {
  return new Promise((resolve, reject) => {
    // resolve is invoked to indicate a success, reject is a failure
    // if a value is passed to resolve, it will be caught as the first argument to .then()
    // if a value is passed to reject, it will be caught as the first argument to .catch(), or the second argument to .then()
    setTimeout(() => resolve(), numberOfSeconds * 1000);
  });
}
  1. Use Promises to write more maintainable asynchronous code
    // Without Promises, we have to nest our code.
    // These can get very confusing; this is a simple example, but it's already hard to tell what each setTimeout's delay is connected to.
    setTimeout(() => {
        console.log(message)
        setTimeout(() => {
            console.log(message.toUpperCase() + "!")
            setTimeout(() => {
                console.log(message + "?")
                setTimeout(() => {
                    console.log(message.toLowerCase() + "...")
                }, 1 * 1000)
            }, 3 * 1000)
        }, 2 * 1000)
    }, 1 * 1000)
    
    // With Promises, we write more code up front in order for us to have more readable and maintainable code
    // We define our promises
    function promise1(message, delay) { // "hello"
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(message) // "hello"
            }, delay * 1000)
        })
    }
    
    function promise2(message, delay) { // message = "hi"
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(message.toUpperCase() + "!") // "HI!"
            }, delay * 1000)
        })
    }
    
    function promise3(message, delay) { // "hey"
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(message + "?") // "hey?"
            }, delay * 1000)
        })
    }
    
    function promise4(message, delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(message.toLowerCase() + "...") // "what's up..."
            }, delay * 1000)
        })
    }
    
    // Then we chain can chain them however we like.
    // Returning our strings from our Promises is adding flexibility to our code, allowing us to use the results however we like.
    // We replaced the complicated nesting with more modular chaining of .then
    promise1("hello", 1)
        .then(res1 => {
            console.log(res1); // "hello"
            return promise2("hi", 2);
        })
        .then(res2 => {
            console.log(res2); // "HI!"
            return promise3("hey", 3);
        })
        .then(res3 => {
            console.log(res3); // "hey?"
            return promise4("what's up", 1);
        })
        .then(res4 => {
            console.log(res4); // "what's up..."
        });
  2. Use the fetch API to make Promise-based API calls
// init is an optional object argument to customize the method (default is 'GET'), headers, or body of the request
// For example, it could take the form:
    // const init = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{"title": "Sir", "name": "Robin"}' }
fetch(url, init).then(response => {
    // do something with the response
    // common first action to take would be parsing the response
        // parsing json with response.json(), or text with response.text()
}).then(data => {
    // since fetch is returning a promise, we can chain on as many .then calls as we need
})
  1. Use async/await with promise-based functions to write asynchrnous code that behaves synchronously.
// Without async/await, we can use .then chains
// We use a .catch method to catch errors
function wrapper() {
  promise1("hello", 1)
    .then(res1 => {
      console.log(res1);
      return promise2("hi", 2);
    })
    .then(res2 => {
      console.log(res2);
      return promise3("hey", 3);
    })
    .then(res3 => {
      console.log(res3);
      return promise4("what's up", 1);
    })
    .then(res4 => {
      console.log(res4);
    })
    .catch(err => {
      console.error("Error encountered:", err)
    });;
};

wrapper();

// With async/await, our code looks more like synchronous code
// We use a standard try/catch block to handle errors
// In order for us to use `await` we must be in a function declared with `async`
async function wrapper() {
  try {
    console.log(await promise1("hello", 1));
    console.log(await promise2("hi", 2));
    console.log(await promise3("hey", 3));
    console.log(await promise4("what's up", 1));
  } catch (err) {
    console.error("Error encountered:", err)
  }
}

wrapper();

HTML

- contains metadata for the HTML
- often will include a <title> as well as <link> and <script async> tags