0%

COSI 152A Web Application Development

尚硅谷 Node.js 教程

The fs module

  1. Write content to a file asynchronously

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.writeFile("./file1.txt", "writeContent", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Write Succeeded");
    });
  2. Write content to a file synchronously

    1
    2
    3
    const fs = require("fs");

    fs.writeFileSync("./file1.txt", "writeContent");
  3. Append content to a file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const fs = require("fs");

    // This is equivalent to fs.writeFile('./file1.txt', '\nappendContent', 'a', (res) => {...});
    fs.appendFile("./file1.txt", "\nappendContent", (err) => {
    if (err) {
    console.log("Error occured");
    return;
    }
    console.log("Append succeeded");
    });

    // This is equivalent to fs.writeFileSync('./file1.txt', '\nappendContent', {options: 'a'});
    fs.appendFileSync("./file.txt", "\nappendContent");
  4. Write content stream to a file

    1
    2
    3
    4
    5
    6
    const fs = require("fs");

    const ws = fs.createWriteStream("./file1.txt");
    ws.write("writeContent1\n");
    ws.write("writeContent2\n");
    ws.close();
  5. Read content from a file asynchronously

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const fs = require("fs");

    fs.readFile("./file1.txt", (err, data) => {
    if (err) {
    console.log("Error occured");
    return;
    }
    // data is a buffer, need toString method to print its content
    console.log(data.toString());
    });
  6. Read content from a file synchronously

    1
    2
    3
    4
    const fs = require("fs");

    const data = fs.readFileSync("./file1.txt");
    console.log(data.toString());
  7. Read content stream from a file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const fs = require("fs");

    const rs = fs.createReadStream("./file1.txt");

    rs.on("data", (chunk) => {
    // default chunk size is 65kb
    console.log(chunk);
    });

    rs.on("end", () => {
    console.log("Read finished");
    });
  8. Reanme a file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require("fs");

    fs.rename("./fromPath.txt", "./toPath.txt", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Rename finished");
    });
  9. Delete a file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const fs = require("fs");

    fs.unlink("./file1.txt", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("File deleted");
    });

    fs.unlinkSync("./file11.txt");

    fs.rm("./file2.txt", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("File deleted");
    });

    fs.rmSync("./file22.txt");
  10. Directory operations

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    const fs = require("fs");

    // Create a directory
    fs.mkdir("./dir1", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Directory created");
    });

    // Recursively create directory
    fs.mkdir("./a/b/c", { recursive: true }, (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Directory created");
    });

    // Notice fs.rmdir is deprecated, use fs.rm instead
    // Delete an empty directory
    fs.rmdir("./dir/b/c", (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Directory created");
    });

    // Recursively delete a directory
    fs.rmdir("./dir/b/c", { recursive: true }, (err) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log("Directory created");
    });
  11. Check resource status

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const fs = require("fs");

    fs.stat("./fil1.txt", (err, data) => {
    if (err) {
    console.log(err);
    return;
    }
    console.log(data);
    // Is target a file
    console.log(data.isFile());
    // Is target a directory
    console.log(data.isDirectory());
    });
  12. The relative path that fs module uses is not relative to the path of the node.js file, it is actually relative to the command line path (the path where you enter the node xxx.js command). Inorder to fix this bug, you can use absolute path. A trick to get the absolute path is to use the __dirname variable to get the absolute path of current folder, so __dirname + /xxx.js is the absolute path of xxx.js file

The path module

  1. Get formal absolute path with correct delimiter

    1
    2
    3
    4
    5
    6
    7
    const fs = require("fs");
    const path = require("path");

    // This one works as a valid path, but it is not reabably correct
    const wrongAbsPath = __dirname + "/index.html";
    // This absolute path has the correct ddelimiters
    const corrAbsPath = path.resolve(__dirname, `./index.html`);
  2. Get system separator: path.sep

  3. Get the absolute path of the current directory: __dirname

  4. Get the absolute path of the current file: __filename

  5. Get a JSON of the absolute path: path.parse(pathURL)

  6. Get the basic file name: path.basename(pathURL)

  7. Get the directory name: path.basename(pathURL)

  8. Get the file extension name: path.basename(pathURL)

HTTP

  1. HTTP request
    • Format: request line + request header lines + request body
    • Request line: method + URL + HTTP version
    • Request header: key-value pairs for metadata
    • Request body: can contain any data
  2. HTTP response
    • Format: response line + response header lines + response body
    • Response line: HTTP version + status code + reason phrase
    • Response header: key-value pairs for metadata
    • Response body: can contain different types of data

The http nodule

  1. Create a simple http server

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const http = require("http");

    // The callback is invoked the the server receives a request
    const server = http.createServer((req, res) => {
    // Display content in utf-8, so some contents like Chinese can be
    // displayed correctly
    res.setHeader("content-type", "text/html;charset=utf-8");
    res.end("Server response here我们");
    });

    // The callback is invoked when the server starts
    server.listen(9000, () => {
    console.log("Server Started");
    });
  2. Default port for HTTP is 80, default port for HTTPS is 443

  3. Get request method req.method

  4. Get URL: req.url, for localhost:9000/test, this returns /test

  5. Get request HTTP version: req.httpVersion

  6. Get request header: req.headers, return an object with all keys in lowercase

  7. Get request body

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const http = require("http");

    const server = http.createServer((req, res) => {
    let body = "";

    req.on("data", (chunk) => {
    // body += chunk also works, the chunk buffer will be automatically
    // converted to string
    body += chunk.toString();
    });

    req.on("end", () => {
    console.log(body);
    res.end("Server response here");
    });
    });

    server.listen(9000, () => {
    console.log("Server started");
    });
  8. Get request URL and query string with the url module

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const http = require("http");
    const url = require("url");

    const server = http.createServer((req, res) => {
    // The second argument converts the query from string to object
    const res = url.parse(req.url, true);
    const pathname = res.pathname;
    console.log(pathname);
    const value = res.query.key;
    console.log(value);
    res.end("Server response here");
    });

    server.listen(9000, () => {
    console.log("Server started");
    });
  9. Another way to get query value without use of url module

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const http = require("http");

    const server = http.createServer((req, res) => {
    const url = new URL(req.url, "http://localhost");
    let value = url.searchParams.get(key);
    console.log(value);
    });

    server.listen(9000, () => {
    console.log("Server started");
    });
  10. Set response status code: res.statusCode = xxx

  11. Set response status message: res.statusMessage = xxx

  12. Set response header: res.setHeader(key, value), multiple values with one key: res.setHeader(key, [values])

  13. Set response body: res.write(content); res.end();

  14. Notice let docs = document.getElementsByTagName(name) returns a HTMLCollection and cannot use forEach directly, you need to use Array.from(docs).forEach() let docs = document.querySelectorAll(name) returns a NodeList and can use forEach directly

  15. Respond to different file types

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const http = require("http");
    const fs = require("fs");

    const server = http.createServer((req, res) => {
    const url = req.url;
    if (url === "/") {
    res.setHeader("content-type", "text/html;charset-utf-8;");
    const html = fs.readFileSync(__dirname + "/response.html");
    res.end(html);
    } else if (url.includes(".css")) {
    const css = fs.readFileSync(__dirname + "/style.css");
    res.end(css);
    } else if (url.includes(".js")) {
    const script = fs.readFileSync(__dirname + "/script.js");
    res.end(script);
    } else {
    res.statusCode = 404;
    res.end("<h1> 404 Not Found.</h1>");
    }
    });

    server.listen(9000, () => {
    console.log("Server started");
    });
  16. Absolute Path and Relative path

    • Absolute path
    • https://www.google.com directly send request to the url, used with external links
    • //test.com/web combine URL with current portocol and then send request, example http://test.com/web
    • /test.com/web combine URL with current portocol, server name, port number and then send request, example http://localhost:9000/test.com/web
    • Relative path: do calculation first, then send request. You should use absolute path instead of relative path when possible
  17. Set MIME(Multipurpose Internet Mail Extensions) type

    • Use file extension to get MIME type
    1
    2
    3
    4
    const pathname = url.parse(req.url, true);
    const filePath = __dirname + pathname;
    // remote . in the extension name
    const ext = path.extname(filePath).slice(1);
    • Use a map from extension name to MIME types
    1
    2
    3
    4
    5
    6
    7
    8
    let type = mappng[ext];
    if (type) {
    // if content type is valid
    res.setHeader("content-type", type);
    } else {
    // used for unknown binary files
    res.setHeader("content-type", "application/octet-stream");
    }
  18. Charset problem

    • res.setHeader('content-type', 'test/xxx;charset=utf-8');
    • In html, you can set <meta charset="UTF-8">. But response heder has high priority if the mata and response header have different charsets
    • You do not need to charsets for stylesheets and scripts, they will automatically be converted to the same charset as the HTML file by browser
    • Handling process
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let type = mapping[ext];
    if (ext === "html") {
    type += ";charset=utf-8";
    }
    if (type) {
    // if content type is valid
    res.setHeader("content-type", type);
    } else {
    // used for unknown binary files
    res.setHeader("content-type", "application/octet-stream");
    }
  19. Error Handling

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Method not allowed
    if (req.method !== "GET") {
    res.statusCode = 405;
    res.end("<h1>405 Method Not allowed</h1>");
    // Should have return here
    return;
    }
    if (err) {
    switch (err.code) {
    // No file or directory
    case "ENOENT":
    res.statusCode = 404;
    res.end("<h1>Resource Not Found</h1>");
    // Operation not allowed
    case "ETERM":
    res.statusCode = 403;
    res.end("<h1>403 Forbidden</h1>");
    default:
    res.statusCode = 500;
    res.end("<h1>Server internal error</h1>");
    }
    }

Modularity

  1. Export data
    1
    2
    module.exports = { a, b };
    exports.a = a;
    • requirex(xxx) retrieves the module.exports object in a module
    • Initially, exports = module.exports = {}
    • Use relative path for user-made module, extension for js file and json file can be omitted
    • require(directory)
      1. Checks the package.json, finds the main property, and import the corresponding value file
      2. If main property or package.json does not exist, tries to import index.js and index.json
      3. Raises an error
    • Node.js is compatible with CommonJS(require style) and ECMAScript(import style)
  2. npm
    • npm -v, npm init -y npm s <name>(search package), you can also search packages on https://www.npmjs.com/, npm i <name>@<version>(install package for a specific version), npm r <name>(remote package) pack-lock.json(used to hold information about exact package version number)
    • For NPM packages, simply write its name in the require function, this will allow npm to search hierarchically for the package upward until the root directory of the disk
    • Some packages are development dependencies(npm i -S <name>), some packages are production dependencies(npm i -D <name>)
    • Global installation: npm i -g <name>, global uninstallation: npm r -g <name>]
    • Run scripts: npm run <script-name>, for scripts with key start, you can simply use npm start
  3. yarn
    • yarn is a fast, secure and reliable node package manager, npm i -g yarn
    • yarn init -y, yarn add <name>, yarn (install add required dependencies), yarn <script-name>, yarn global install <name>(need to add the global package folder to the path environment variable)
    • yarn.lock is the lockfile for yarn-managed project
    • Only use npm or yark for a project, do not mix them together
    • Package managers
      • composer for PHP, pip for Python, maven for Java, go mod for go, npm/yarn for JavaScript, rubyGems for Ruby
      • yum for Centos, apt for Ubuntu, homebrew for MacOS, chocolatey for Windows
  4. nvm
    • Node Version Manager, used to switch Node.js version
    • nvm install x.x.x, nvm use x.x.x, nvm uninstall x.x.x, nvm list

Express.js

  1. An example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const express = require("express");
    const port = 9000;

    const app = express();

    app.get("/", (req, res) => {
    res.end("<h1> Hello Express.</h1>");
    });

    app.get("/:id", (req, res) => {
    const id = req.params["id"];
    res.end(`<h1>Hello Page ${id}</h1>`);
    });

    app.listen(port, () => {
    console.log(`Server started on port ${port}`);
    });
  2. Router: router.method(url, callback), all, get, post, ...

  3. Request

    • Get some parameters:

      • Native parameters: req.method, req.url, req.httpVersion, req.headers
      • Express parameters: req.path, req.query, req.ip
    • Get URL parameters: req.params[key] or req.params.key

    • Get Request Body using the body-parser package(npm i body-parser)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const bodyParser = require("body-parser");

      // Define body parse for application/x-www-form-urlencoded data
      const urlencodedParser = bodyParser.urlencoded({ extended: false });

      // Use router-level body parser
      // After body-parser is executed, there will be a body attribute in the req object,
      // then you can access data using req.body.key
      app.post("/login", urlencodedParser, (req, res) => {
      console.log(req.body);
      res.end();
      });
  4. Response

    • Native parameters: res.statusCode, res.statusMessage, res.write(), res.end()
    • Express method: res.send(), this method will automatically set content-type in response headers
    • res.redirect(url), res.download(resourceAbsolutePath), res.json(jsonObject), res.sendFile(resourceAbsolutePath) you need to set content-type in header for some file types, otherwise files like JSON will be downloaded instead of being displayed in the brwoser
  5. Middleware

    • Application-level middleware: handle all requests

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const recordMiddleware = (req, res, next) => {
      let { url, ip } = req;
      fs.appendFileSync(
      path.resolve(__dirname, "./accesss.log"),
      `Requested ${url} from ${ip} \n`
      );
      // You need to call next function to continue to execute
      next();
      };

      app.use(recordMiddleware);
    • Router-level middleware: handle requests to URLs specified by a router. Can be used for user authentication

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      const checkSecret = (req, res, next) => {
      if (url.query.secret === "secret") {
      // If secret is corret, continue to execute
      next();
      } else {
      res.send("<h1>Wrong secret </h1>");
      }
      };

      // When requests is made to /admin, the middleware function is executed first
      app.get("/admin", checkSecret, (req, res) => {
      res.send("<h1>Admin Page</h1>");
      });
    • Built-in middleware for static resources

      1
      app.use(express.static(__dirname + "/public"));
      • This middleware will search for index.html that is directly located inside the public folder, and will use the html to respond requests to /
      • If you set router to handle requests to /, then it depends the order that whether your router or the static resource middleware is executed first
      • This static resource middleware has nothing to do with the absolute path in the res.sendFile(path) function, you still need to specify the absolute paht
        1
        2
        3
        4
        app.get("/login", (req, res) => {
        let filePath = path.join(__dirname, "./public/login.html");
        res.sendFile(filePath);
        });
  6. Anti-leech protection(防盗链)

    • Resources can only be accessed from the hosting website, and cannot be accessed from other websites with a link to the original hosting website

    • Idea: check the referer attribute in the request header, only send response when referer is the same as the hosing website

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const checkReferer = (req, res, next) => {
      // get attribute from request header
      let referer = req.get("referer");
      if (referer) {
      let url = new URL(referer);
      if (url.hostname != "127.0.0.1") {
      res.statusCode = 404;
      res.send("<h1>404 Not Found</h1>");
      return;
      }
      }
      next();
      };

      app.use(checkReferer);
  7. Router modularity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // in homerouter.js
    const express = require("express");
    const path = require("path");
    const router = express.Router();

    router.get("/home", (req, res) => {
    let loginPath = path.join(__dirname, "./public/index.html");
    res.sendFile(loginPath);
    });

    router.get("/login", (req, res) => {
    let loginPath = path.join(__dirname, "./public/login.html");
    res.sendFile(loginPath);
    });

    module.exports = router;
    1
    2
    3
    4
    // in app.js
    const homeRouter = require("./06homerouter");

    app.use("/", homeRouter);
  8. EJS template

    • An example

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const ejs = require("ejs");
      const fs = require("fs");

      const country = "United States";
      const title = "EJS example";

      let template = fs.readFileSync("./07learn-ejs.ejs").toString();
      let result = ejs.render(template, { title: title, country: country });

      console.log(result);
    • Control flow: notice that you can use -%> instead of %> to trim empty lines in the control flow statements

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const ejs = require("ejs");
      const fs = require("fs");

      const arr = ["name1", "name2", "name3", "name4"];
      const title = "EJS Loop";

      let template = fs.readFileSync("./08ejs-loop.html").toString();
      let result = ejs.render(template, { arr: arr, title: title });

      console.log(result);
    • Use EJS in express

      • Set template engine: app.set("view engine", "ejs");
      • Set templates location directory: app.set("views", path.resolve(__dirname, "./views"));, the template files should have extension .ejs
      • Render EJS templates res.render("templatename", dataObject)
      • File hierarchy: for view/auth/auth.js, the render method should be res.render(auth/auth.js)
  9. Express application generator

    • Installation: npm i -g express-generator
    • Generate project: express --view=ejs project, or express -e project
    • The template project uses two middlewares to let you access body: app.use(express.json()); for application/json, and app.use(express.urlencoded({ extended: false })); for application/x-www-form-urlencoded, so you can use req.body directly
    • Express port settings
      • Use process.env.PORT or a default port in express
        1
        2
        3
        // the normalizePort method checks if process.env.PORT is a valid number
        const port = normalizePort(process.env.PORT || '8080');
        app.set('port', port);
      • Set environment variables: in Command Prompt: set PORT=5000, in Power Shell: $env:PORT=5000, in Bash/Mac export PORT=5000
  10. File Upload

    • Form attribute for file upload
      1
      2
      3
      4
      5
      <!-- The enctype is required for file upload -->
      <form action="/url" method="post" enctype="multipart/form-data">
      <label for="portrait">Image</label>
      <input type="file" id="portrait" name="portrait" /><br />
      </form>
    • Notice the built-in express middleware cannot handle file upload with enctype=multipart/form-data, you need external packages to handle multipart like multer or formidable
      • Logic: when a file is uploaded to the server, the file itself is saved in the static resource directory on the server, its corresponding new url is saved into database for future reference
      • An example using formidable
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        const formidable = require("formidable");
        // Other codes are omitted
        router.post("/register", (req, res) => {
        // Create form object
        const form = formidable({
        multiples: true,
        // Let the file be uploaded into the following static resource directory
        uploadDir: __dirname + "/../public/files",
        // Keep file extension
        keepExtensions: true,
        });
        form.parse(req, (err, fields, files) => {
        if (err) {
        next(err);
        return;
        }
        // Get the new file url for future request, this url points to the file location in the static resource directory
        let url = "/files/" + files.portrait.newFilename;
        res.json({ fields, url: url });
        });
        });
  11. The lowdb module

    • A simple to use local JSON databse. Use native JavaScript API to query. Written in TypeScript
    • Notice lowdb is now a pure ESM package(the ECMAScript style), in order to use CommonJS style, use lowdb 1.0.0 npm i lowdb@1.0.0
    • Some methods like write(), push(), assign(), remove(), ...

MongoDB

  1. Introduction

    • database -> collection -> document
    • show dbs, use <db>, db, db.createCollection(<collection>), db.dropDatabase()
    • show collections, db.<collection>.drop, db.<collection>.renameCollection(<collection>)
    • db.<collection>.insert(<document>), db.<collection>.find(<condition>), db.<collection>.updateOne/updateMany(<condition>, {$set: <new-value>}), db.<collection>.deleteOne/deleteMany(<condition>)
    • You can use mongo shell to connect to MongoDB: run mongosh in cmd, and you will connect to the local MongoDb database running at localhost:27017
  2. The mongoose module

    • Installation: npm i mongoose --save
    • In mongoose 7.x.x version, support for callback is removed (Reference: 7.0.0-rc0 / 2023-02-23 update), so you need npm i mongoose@6.10.0 in order to use callbacks
    • An example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      const mongoose = require("mongoose");

      const db = "cosi152";
      mongoose.connect(`mongodb://127.0.0.1:27017/${db}`);

      mongoose.connection.once("open", () => {
      console.log("Connected to database");
      });

      mongoose.connection.on("error", () => {
      console.log("Error occured");
      });

      mongoose.connection.on("close", () => {
      console.log("Connection terminated");
      });

      setTimeout(() => {
      mongoose.disconnect();
      }, 2000);
    • Insert document: <Model>.create()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      mongoose.connection.on("open", () => {
      console.log("Connected to database");
      let accountSchema = new mongoose.Schema({
      id: Number,
      name: String,
      age: Number,
      });
      let bookModel = mongoose.model("accounts", accountSchema);
      bookModel.create({ id: 3, name: "Wendy", age: 22 }, (err, data) => {
      if (err) {
      console.log(err);
      }
      console.log(data);
      });
      });
    • Schema datatype: String, Number, Boolean, Array, Date, Buffer(can store binary files, but static resources are commonly stored on the server static resource directory instead), Mixed(mongoose.Schema.Types.Mixed), ObjectId(commonly used for foreign key), Decimal128
    • Mongoose field validation: required: true, default: <value>, enum: [...<values>], unique: true(in order for unique to have effect, you need to drop and then recreate the collection)
    • Delete document: <Model>.deleteOne(<condition>, <callback>), <Model>.deleteMany(), ...
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      mongoose.connection.on("open",  () => {
      console.log(`Databse connected to ${url}`);
      const arr = await BookModel.find();
      BookModel.deleteOne({ _id: "6418b5cf5cbd30962f396157" }, (err, data) => {
      if (err) {
      console.log(err);
      return;
      }
      console.log(data);
      });
      });
    • Update document: <Model>.updateOne(<condition>, <newValue>, <callback>), <Model>.updateMany(), ...
      1
      2
      3
      4
      5
      6
      7
      8
      9
      mongoose.connection.on("open", () => {
      console.log(`Connected to ${url}`);
      BookModel.updateOne({_id: "6418b5cf5cbd30962f396158"}, {name:"HongLouMeng"}, (err, data) => {
      if (err) {
      console.log(err);
      }
      console.log(data);
      });
      });
    • Query document:<Model>.find(<condition>, <callback>), <Model>.findById(<id>, <callback>), <Model>.findOne(<condition>, <callback>), ...
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      mongoose.connection.on("open", () => {
      console.log(`Connected to ${url}`);
      BookModel.findOne({_id: "6418b5cf5cbd30962f396158"}, (err, data) => {
      if (err) {
      console.log(err);
      }
      console.log(data);
      console.log(data.length);
      });
      });
    • Conditions:
      • Operators: $lt, $gt, $lte, $gte, $ne, {price: {$lt: 10}}
      • Logic operators: $or, $and, { $and: [{ price: { $lt: 20 } }, { price: { $gt: 10 } }] }
      • Regular expression: {name: /123/}, {name: new RegExp("123")} (can be used to match values passed from variables)
    • Customized query
      • Only return specific fields from queries: BookModel.find().select(field: 1).exec(callback), only fields with value 1 in the select method will be returned
      • Sort results: BookModel.find().sort(price: 1).exec(callback),1 means ascending, -1 means descending
      • Limit results with offsets: BookModel.find().sort(price: 0).skip(m).limit(n).exec(callback), select n items with top m items skipped
    • Modularized mongoose connection
      1
      2
      3
      4
      5
      6
      7
      // config.js

      module.exports = {
      dbhost: "127.0.0.1",
      dbport: 27017,
      dbname: "cosi152"
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      // dbConnect.js

      const mongoose = require("mongoose");
      const {dbhost, dbport, dbname} = require("./config");

      const dbConnect = (success, fail) => {
      const db = "cosi152";
      const url = `mongodb://${dbhost}:${dbport}/${db}`;

      mongoose.connect(url);

      mongoose.connection.on("open", () => {
      console.log(`Connected to ${url}`);
      success();
      });
      mongoose.connection.on("error", () => {
      console.log("Error occured");
      fail();
      });
      mongoose.connection.on("close", () => {
      console.log(`Disconnected from ${url}`);
      });

      setTimeout(() => {
      mongoose.disconnect();
      }, 2000);
      };

      module.exports = dbConnect;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // usage.js

      const mongoose = require("mongoose");
      const BookModel = require("./book");
      const dbConnect = require("./db");

      const success = () => {
      BookModel.find({ price: { $gt: 10 } })
      .select({ author: 1, price: 1 })
      .sort({ price: 1 })
      .limit(3)
      .exec((err, data) => {
      console.log(data);
      });
      };

      const fail = () => {};

      dbConnect(success, fail);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      // book.js

      const mongoose = require("mongoose");

      const BookSchema = new mongoose.Schema({
      name: {
      type: String,
      required: true,
      },
      author: {
      type: String,
      required: true,
      },
      price: {
      type: Number,
      required: true,
      },
      is_hot: {
      type: Boolean,
      required: true,
      },
      });

      const BookModel = mongoose.model("novels", BookSchema);

      module.exports = BookModel;
    • Promise chain
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      new movieModel({ title: "The Wandering Earth 2", director: "Fan Guo" })
      .save() // save in the database, return saved document
      .then((movie) => {
      console.log(`Saved movie: ${movie}`); // print saved document
      return movieModel.findOne({ title: movie.title }); // find a document, return found document
      })
      .then((movie) => {
      console.log(`Found movie:, ${movie}`); // print found document
      return movieModel.findOneAndUpdate( // find a document and update it
      { title: movie.title },
      { director: "123" },
      { returnDocument: "after" } // return updated document rather than the original one
      );
      })
      .then((movie) => {
      console.log(`Updated movie: ,${movie}`); // print updated document
      return movieModel.findOneAndDelete({ title: movie.title }); // find a document and delete it, return deleted document
      })
      .then((movie) => {
      console.log(`Delete movie: ${movie}`); // print deleted document
      return movieModel.find(); // find all documents in the database
      })
      .then((results) => {
      console.log(`Data in the database: ${results}`); // print all documents in the database
      })
      .catch((error) => console.log("Error:", error));
  3. Graphical MongoDB Tools

    • Robo 3T
    • Navicat for MongoDB
    • MongoDB Atlas
  4. Convert string to Date: use the moment package

    • moment("2023-03-21") convert string to object
    • moment("2023-03-21").toDate() convert object to Date object
    • moment(new Date()).format("YYYY-MM-DD") convert date to YYYY-MM-DD format
  5. Use alert to double confirm deletion

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let btns = document.querySelectorAll(".myDelBtn");
    btns.forEach((item) => {
    item.addEventListener("click", (e) => {
    // If user clicks yes, then return true, if the user clicks cancel, return false
    if (confirm("Do you really want to delete the item?")) {
    return true;
    } else {
    e.preventDefault();
    }
    });
    });

  6. API

    • The interface connecting frot-end and back-end
    • The data interface usually transfers data using JSON
    • API = method + URL + request parameters + reponse
  7. RESTful API

    • Characteristics

      • URL represent resource
      • Type of resource operation shoule be the same as request type
      • Response should contain proper status code
    • An example

      Operation Request Method URL Returned Information
      Add a song POST /song Return info of added song
      Delete a song DELETE /song/id Return empty document
      Modify a song PUT /song/id Return info of updated song
      Modify a song PATCH /song/id Return info of updated song
      Get all songs GET /song Return info array of all songs
      GET a song GET /song/id Return info of a specific song
    • The json-server package

      • Get a full fake REST API with zero coding for testing
      • Usage
        • Install package npm i -g json-server
        • Create a json file called db.json, write your data structure
        • Run json-server --watch db.json in command line in the same directory as the db.json file
        • The server will start at localhost:3000
    • API testing tools: apipost, postman

    • Add API for project

      • Add an api router
      • Let the router return json format data
      • Common JSON format
        1
        2
        3
        4
        5
        {
        code: res.statusCode,
        msg: res.statusMessage,
        data: data
        }
  8. Session Control

    • Common session control techniques: cookie, session, token
    • Cookie
      • data saved in browser, same hostname has same cookie, hostname cookie is wrapped in request header inside cookie attribute
      • Cookie is sent from server inside response header set-cookie attribute, and the browser saved cookie locally
      • Use cookie in express:
        • res.cookie(key, value), for multiple cookies, simply call this method multiple times. Cookies set using the above method expires when the browser is closed.
        • res.cookie(key, value, {maxAge: <milliseconds>}), cookie set in this way only expires when reaches its max age. when you use browser inspector, the maxAge is displayed in seconds
        • res.clearCookie(key) removes cookie specified by the key
        • User cookie-parser to get cookie from request
          • Install npm i cookie-parser
          • An example
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            const cookieParser = require("cookie-parser");

            app.user(cookieParser);

            app.get('/', function (req, res) {
            // Cookies that have not been signed
            console.log('Cookies: ', req.cookies);

            // Cookies that have been signed
            console.log('Signed Cookies: ', req.signedCookies);
            res.json(req.cookies);
            })

            app.listen(8080)
    • Session
      • Data saved on the server, can be used for user authentication: when user tries to login, server compare credentials in the database. If the info matches, server will create a session object with an unique session id and save the session object on the server. Then the server will set session id as cookie inside response header, so for later on user logins, the server knows user identity
      • Use session in express and save session to MongoDB
        • Installation: npm i express-session connect-mongo, express-session stores session in memory by default, we can use connect-mongo to store the session object into MongoDB
        • App session configuration
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          app.use(
          session({
          // the name of the cookie that server sets in its response
          name: "sid",
          // the secret used to sign the session id cookie(generate hash session id), can be string or array of strings
          secret: "yanxuanshaozhu",
          // whether each request should be made a session
          saveUninitialized: false,
          // force the session be saved back to the session store, so user credentials are valid
          resave: true,
          // save session to MongoDB, the collection name is sessions
          store: MongoStore.create({
          mongoUrl: "mongodb://127.0.0.1:27017/cosi152",
          }),
          // set options for the cookie in the response
          cookie: {
          // if this is true, then client cannot access the session cookie in the response
          httpOnly: true,
          maxAge:1000 * 60
          },
          })
          );
        • Let the server set session
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          router.post("/login", (req, res) => {
          let { username, password } = req.body;
          userModel.findOne(
          { username: username, password: md5(password) },
          (err, data) => {
          if (err) {
          res.status(500).send("Registration failed");
          return;
          }
          if (!data) {
          return res.send("Login failed");
          }
          // User successfully logined, set session cookie in response
          req.session.username = data.username;
          req.session._id = data._id;
          res.render("success", {
          msg: "Login success",
          url: "/account",
          title: "Success Page",
          });
          }
          );
          });
        • Use session to check login status middleware:
          1
          2
          3
          4
          5
          6
          7
          module.exports = (req, res, next) => {
          if (!req.session.username) {
          res.redirect("/loign");
          }
          // Do not forget to call next method to continue to execute other logics
          next();
          };
    • Password encryption
      • Password should be encrypted before being stored inside the database
      • Use md5 moduel to do password hashing: npm i md5, password = md5(req.body.password)
    • Protect CSRF
      • Cross-Site Request Forgery (CSRF) is an attack that forces authenticated users to submit a request to a Web application against which they are currently authenticated
      • Example: in website B, it sends a get request to website A with a cookie from website A, then user logging status can be changed on website A
      • How to deal with this: Use POST request instead of GET request
    • token
      • Definition: a encrypted string sent from user to client to identify client identity
      • tokent is commonly used in mobile apps for session controls
      • cookie is automatically included in request header, but a client needs to manually include tokens inside the header of its requests
      • Advantage of token
        • Saved on client, so less pressure on the server
        • Can prevent CSRF
        • Tokens can be shared among services
    • JWT(JSON Web Token)
      • Enable secure claims between two parties as a JSON object
      • Use JWT in javascript: npm i jsonwebtoken
      • Logic
        • When client side credentials match records stored in the database, server will creat a token and send it to client
        • For later on requests, the token is included in the request and sent to the server

尚硅谷其他教程

AJAX

  1. AJAX Basics
    • Originally AJAX uses XML, now JSON is used more frequently
    • AJAX GET Example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <script type="text/javascript">
      let btn = document.getElementById("send");
      let result = document.getElementById("result");
      btn.addEventListener("click", () => {
      // Create XHR object
      const xhr = new XMLHttpRequest();
      // Specify request method and request URL
      // If you send get request with query string, modify the request URL
      xhr.open("GET", "http://127.0.0.1:9000/server?id=1&name=2");
      // Send HTTP request
      xhr.send();
      // xhr.readystate an integer value representing http request status
      xhr.onreadystatechange = () => {
      // all data is returned from the server
      if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300) {
      console.log(xhr.status); // response status
      console.log(xhr.statusText); // response status mesage
      console.log(xhr.getAllResponseHeaders); // response header array
      console.log(xhr.response); // response body
      result.innerHTML = xhr.response;
      }
      }
      };
      });
      </script>
    • AJAX POST Example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <script type="text/javascript">
      let btn = document.getElementById("send");
      let result = document.getElementById("result");
      btn.addEventListener("click", () => {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", "http://127.0.0.1:9000/server");
      xhr.setRequestHeader("Content-Type", "application/json");
      const data =JSON.stringify({ id: "id1", name: "name1" });
      xhr.send(data);
      // xhr.readystate an integer value representing http request status
      xhr.onreadystatechange = () => {
      // all data is returned from the server
      if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300) {
      result.innerHTML = xhr.responseText;
      }
      }
      };
      });
      </script>
    • Use XHR to set request type: xhr.setRequestHeader("key", "value")
    • If response data is JSON string, how to deal with it
      • Manunally modify response: const data = JSON.parse(xhr.response); console.log(data.id);
      • Set response type, then response will be automatically converted to JSON object: res.responseType = "json"; console.log(xhr.response.id);
    • Timeout handling and error handling
      1
      2
      3
      4
      5
      6
      7
      8
      xhr.timeout = 2000;  // 2000 milliseconds
      xhr.ontimeout = () => {
      alert("Request cancelled due to timeout");
      }

      xhr.onerror = () => {
      alert("Request cancelled due to error");
      }
    • Cancel request before receiving response: xhr.abort()
    • Prevent resending the same request:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      let isSending = false;
      let xhr = null;
      // Cancel current request
      if (isSending) {
      xhr.abort();
      }
      xhr = new XMLHttpRequest();
      // Start sending request
      isSending = true;

      xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
      // Response has been received
      isSending = false;
      }
      }
  2. Use AJAX with jQuery
    • jQuery uses XMLHttpRequest in its implementation
    • Send GET Request Example: $.get(url, dataObject, successCallback(data), dataType)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script type="text/javascript">
      $("button")
      .eq(0)
      .click(() => {
      $.get(
      "http://127.0.0.1:9000/serverjson",
      { id: "01", name: "name1" },
      (data) => {
      $(".container div").html(`id: ${data.id}, name: ${data.name}`);
      },
      "json"
      );
      });
      </script>
    • Send POST request example: $.post(url, dataObject, successCallback(data), dataType)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script type="text/javascript">
      $("button")
      .eq(1)
      .click(function () {
      $.post(
      "http://127.0.0.1:9000/serverjson",
      { id: "01", name: "name1" },
      (data) => {
      $(".container div").html(`id: ${data.id}, name: ${data.name}`);
      },
      "json"
      );
      });
      </script>
    • Send request using $.ajax({}) example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      <script type="text/javascript">
      $("button")
      .eq(2)
      .click(() => {
      $.ajax({
      // url
      url: "http://127.0.0.1:9000/serverjson",
      // data
      data: { id: "01", name: "name1" },
      // request type
      type: "GET",
      // response dataType
      dataType: "json",
      // success callback
      success: (data) => {
      $(".container div").html(`id: ${data.id}, name: ${data.name}`);
      },
      //timeout time
      timeout: 2000,
      // error callback
      error: () => {
      alert("Error occured");
      },
      // request headers
      headers: {
      "Content-Type": "application/json",
      },
      });
      });
      </script>
  3. Use AJAX with axios
    • axios uses XMLHttpRequest in its implementation
    • Use axios to send GET request: axios.get(url, {configurations})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      <script type="text/javascript">
      axios.defaults.baseURL = "http://127.0.0.1:9000";
      document.getElementById("btn1").onclick = () => {
      axios
      .get("/serverjson", {
      //url parameters
      params: {
      id: 1,
      name: "name1",
      },
      // request header
      headers: {
      id: 2,
      name: "name2",
      },
      responseType: "json",
      })
      .then((res) => {
      document.getElementById(
      "result"
      ).innerHTML = `id: ${res.data.id}, name: ${res.data.name}`;
      })
      .catch((error) => {
      console.log(error);
      });
      };
      </script>
    • Use axios to send POST request: axios.post(url, dataObject, {configurations})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <script>
      axios.defaults.baseURL = "http://127.0.0.1:9000";
      document.getElementById("btn2").onclick = () => {
      axios.post(
      "/serverjson",
      // data
      {
      id: 1,
      name: "name1",
      },
      {
      // url parameters
      params: {
      id: 2,
      name: "name2",
      },
      // request headers
      headers: {
      id: 1,
      name: "name1",
      },
      }
      );
      };
      </script>
    • Use axios to send request: axios({})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <script>
      axios.defaults.baseURL = "http://127.0.0.1:9000";
      document.getElementById("btn3").onclick = () => {
      axios({
      // request method
      method: "GET",
      // request url
      url: "/serverjson",
      // url parameters
      params: {
      id: 1,
      name: "name1",
      },
      // request headers
      headers: {
      id: 2,
      name: "name2",
      },
      // request body data
      data: {
      id: 3,
      name: "name3",
      },
      })
      .then((response) => {
      document.getElementById(
      "result"
      ).innerHTML = `id: ${response.data.id}, name: ${response.data.name}`;
      })
      .catch((error) => {
      console.log(error);
      });
      };
      </script>
  4. Use fetch api
    • Send get request
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script>
      let btn = document.getElementById("send");
      let result = document.getElementById("result");
      btn.onclick = () => {
      fetch("http://149.28.62.112:3000/server?id=1&name=name", {
      method: "GET",
      })
      .then((response) => response.json())
      .then((data) => {
      data = JSON.parse(data);
      result.innerHTML = `id: ${data.id}, name: ${data.name}`
      });
      };
      </script>
  5. Same origin policy
    • A browser security policy, portocol, hostname, port should be the same
    • By default, ajax should obey the same-origin policy and cannot access cross-origin resources
    • Solution 1: Use jsonp
      • Logic: the script tag is allowed to bypass CORS restrictions because it was designed to enable developers to include JavaScript code from external sources into their web pages
      • JSONP(JSON with Padding) is a technique for making cross-domain requests that bypasses the same-origin policy restrictions implemented by web browsers
      • Client: defined a function in the script, let the src attribute of the script be the get request url. Server: return function call on response data, the function is the one defined in the Client
      • An jsonp example
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // server
        router.get("/server-jsonp", (req, res) => {
        console.log("received get request");
        let data = {
        status: 1,
        msg: "User exists"
        };
        res.send(`handle(${JSON.stringify(data)})`);
        });
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        <!-- client -->
        <script>
        let input = document.getElementById("name");
        let p = document.querySelector("p");
        const handle = (data) => {
        input.style.border = "solid 2px red";
        p.innerHTML = data.msg;
        };
        input.onblur = () => {
        let username = input.value;
        console.log(username);
        // Create a script and let it send get request
        const script = document.createElement("script");
        script.src = "http://149.28.62.112:3000/server-jsonp";
        document.body.appendChild(script);
        };
        </script>
    • Solution 2: configure CORS settings on the server side
      • Idea: Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources
        1
        2
        3
        4
        5
        // server code
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Headers", "*");
        res.setHeader("Access-Control-Allow-Methods", "*");
        res.setHeader("Access-Control-Expose-Headers", "*");

Promise

  1. Promise Basics
    • The promise represents the completion or failure of an asynchronous operation and its resulting value. It was introduced in ES6. Promise or async/await can be used to prevent callback hell
    • Callback hell: nested/ multiple layer of callbacks can cause asynchronouse code become difficult to readand maintain
    • An example
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      <script>
      const rand = (a, b) => Math.ceil(Math.random() * (b - a + 1)) + a - 1;
      const rd1 = document.querySelector("#rd1");
      const rd2 = document.querySelector("#rd2");
      const result = document.querySelector("#result");

      rd1.onclick = () => {
      setTimeout(() => {
      let num = rand(1, 100);
      console.log(num);
      if (num <= 30) {
      result.innerHTML = `You win! Num: ${num}`;
      } else {
      result.innerHTML = `You loose! Num: ${num}`;
      }
      }, 100);
      };
      rd2.onclick = () => {
      new Promise((resolve, reject) => {
      setTimeout(() => {
      let num = rand(1, 100);
      console.log(num);
      if (num <= 30) {
      // pass result value
      resolve(num);
      } else {
      // pass result value
      reject(num);
      }
      }, 100);
      }).then(
      (value) => {
      // succeed
      result.innerHTML = `You win! Num: ${value}`;
      },
      (reason) => {
      // reject
      result.innerHTML = `You win! Num: ${reason}`;
      }
      );
      };
      </script>
    • fs module promise
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      const fs = require("fs");

      fs.readFile("./input.txt", (err, data) => {
      if (err) {
      console.log(err);
      return;
      }
      console.log(data.toString());
      });

      new Promise((resolve, reject) => {
      fs.readFile("./input.txt", (err, data) => {
      if (err) {
      reject(err);
      } else {
      resolve(data);
      }
      })
      }).then((value) => {
      console.log(value.toString());
      }, (reason) => {
      console.log(reason);
      });
    • ajax promise
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      <script>
      const rd1 = document.querySelector("#rd1");
      const rd2 = document.querySelector("#rd2");
      const result = document.querySelector("#result");

      rd1.onclick = () => {
      const xhr = new XMLHttpRequest();
      xhr.open("GET", "http://127.0.0.1:9000/server");
      xhr.send();
      xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.readyState < 300) {
      result.innerHTML = xhr.response;
      } else {
      result.innerHTML = "Error";
      }
      } else {
      result.innerHTML = "Error";
      }
      };
      };

      rd2.onclick = () => {
      new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open("GET", "http://127.0.0.1:9000/server");
      xhr.send();
      xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.readyState < 300) {
      result.innerHTML = xhr.response;
      } else {
      result.innerHTML = "Error";
      }
      } else {
      result.innerHTML = "Error";
      }
      };
      }).then(
      (value) => {
      result.innerHTML = value;
      },
      (reason) => {
      result.innerHTML = `Error ${reason}`;
      }
      );
      };
      </script>
    • util.promisify(callback), the callback should have the error-first style, i.e. (err, data) => {}, this method returns a version that returns promises
      1
      2
      3
      4
      5
      6
      const util = require("util");
      const fs = require("fs");

      const readFilePromise = util.promisify(fs.readFile);

      readFilePromise("./input.txt").then((data) => { console.log(data.toString()) }, (msg) => { console.log(msg); });
  2. Attributes and Execution logic
    • Three states: pending, resolved/fullfilled, rejected
    • pending => resolved/ fulfilled
    • resolve and reject can modify PromiseResult
    • Change promise state: resolve, reject, throw an error(reject)
    • Code template
      1
      2
      3
      new Promise((resolve, reject) => {})
      .then((value) => {}, (reason) => {})
      .catch((reason) => {})
    • Execution order
      • If resolve and reject are sync method, they change the promise state first, then is executed afterwards to handle PromiseResult
      • If resolve or reject is async method, then waits resolve/reject to change the promise state, and is executed afterwards to handle PromiseResult
    • The then method
      • Returns a promise, its result depends on the input of then method. If input callback throws an error, promiseResult of then if error, state is rejected. If input callback does not returns a promise, promiseResult of then is return value of input callback, state is resolved. If input callback returns a callback, it decides the returned promise of the then method
    • If you add catch to the end of a promise chain, then it will be called when any of the asynchronous function calls fails
    • How to interrupt promise chain: return a pending promise somewhere in the chain: return new Promise(() => {});
  3. Methods
    • Promise.resolve(): if input is not a promise, return resolved promise whose PromiseResult is input; if input is a promise, return a promise that has the same state and result as the input promise
    • Promise.resolve(): return a rejected promise whose result is input when input is not a promise, or PromiseResult when input is a promise
    • Promise.all(promises): if all promises in the input array is resolved then resolved, otherwise rejected
    • Promise.race(promises): return result is the same as the first completed promise
  4. async and await
    • async function: returns a promise, the promise state and promiseResult depends on the returning value of the function. If returned value is not a promise, returns a resolved promise whose result is the returned value. If returned value is a promise, the returning promise behaves the same as the returned promise
    • await RHS: if RHS is not a promise, returns the value of RHS. If RHS is a promise, await returns the resolved value of the promise, if the promise is rejected, await throws an error, so it should be surrounded with try...catch. await should be inside an async function, but an async function can have no await keyword

ES New Features

  1. ES6
    • let: cannot define twice, has block scope, does not have variable hoisting
    • const: use uppercase word
    • Destructuring assignment: array, object
    • String template: ${}
    • Object field
      1
      2
      3
      let x = 1;
      let y = 2;
      const obj = {x, y}; // same as const obj = {x: x, y: y};
    • Object method
      1
      2
      3
      4
      5
      6
      const obj = {
      speak() {
      console.log("Speaking");
      }
      // same as speak: () => {console.log("Speaking")}
      }
    • Arrow function: this in arrow function always points to the this object where the function is defined, cannot access arguments object
    • Function argument default value
    • Function ...args arguments: match an array of arguments
    • Spreading operator: ...arr
    • Symbol type: symbol is unique, javascript datatype USONB(undefined, string, symbol, object, null, number, boolean)
      1
      2
      3
      4
      5
      6
      let x = Symbol("xx");
      ley y = Symbol("xx");
      x === y; // false
      let a = Symbol.for("yy");
      let b = Symbol.for("yy");
      a === b; // true
    • Iterator: type that has iterator(Symbol(Symbol.iterator)) can use for...of loop, iterator can be used for user-defined iteration
    • Generator: a function used for async programming
    • Set: let x = new Set([items]), s.size, s.add(x), s.delete(x), s.has(x), can use for...of loop on set
    • Map: m.set(key, value), m.delete(key), m.get(key), can use for...of loop on map
    • class: instance field/method, static field/method, extends