Node. js 101: Promise and async

Source: Internet
Author: User

-- Address: http://blog.chrisyip.im/nodejs-101-package-promise-and-async

First, let's review the Sagase project structure:

lib/    cli.js    sagase.jsGruntfile.jspackage.json

I spoke about it in the previous article.package.json, This articlelib/sagase.js.

Because the code is relatively long, we will talk about it separately in one section. For the complete code, open GitHub.

'use strict';

Notifies the compiler to enter strict mode. The main function is to enable the compiler to help you check some pitfalls. Experienced JavaScript developers can ignore them.

var fs = require('fs-extra'),    async = require('async'),    _ = require('lodash'),    Promise = require('bluebird'),    path = require('path')

Reference all dependencies.--harmony, RecommendedconstReplacevarKeyword to avoid variable modification.

var mar = 'March'mar = 'June'console.log(mar) // 'June'const july = 'July'july = 'May'console.log(july) // 'July'

function readFiles (opts) {}Contains a lot of information, one by one.

return new Promise(function (resolve, reject) {}

Returns a Promise object.

Because Node. js is characterized by Asynchronization, it usually needs to be processed asynchronously:

// get all files in DIRfs.readdir(DIR, function (err, files) {  if (err) {    return errorHandler(err)  }  // loop files  files.forEach(function (file) {    // get the stat of each files    fs.stat(file, function (err, stat) {      // if it's file      if (stat.isFile()) {        // get content of file        fs.readFile(file, function (err, buff) {          // do whatever you want        })      }    })  })})

If you need to process a lot of things in a function, or even make it possible for the function to return results in multiple files, it is difficult to rely only on callback-I don't know when to use the returned results of a file.

If Promise is used, it will be much simpler:

// in a.jsvar Promise = require('bluebird'),    readFilemodule.exports = new Promise(function (resolve, reject) {  fs.readdir(DIR, function (err, files) {    err ? reject(err) : resolve(files)  })})
// in b.jsvar readFile = require('./a.js')readFile  .then(function (files) {    // do something with files    return NEW_RESULT;  }, function (err) {    // handle error here  })  .then(function (data) {    // do something with NEW_RESULT  }, function (err) {    // handle error here  })
// in c.jsvar readFile = require('./a.js')readFile.then(function (files) {  // the files still exist and accessable})

With Promise encapsulation, you canresolveResults across different files, do not need to process everything in a callback function; in addition, through.then()The second function processingrejectTo avoid multiple judgments.

Note that a third-party Promise library named bluebird is introduced here. If the Node. js version is>= 0.11.13Third-party libraries are not required.

async.each(  files,  function (file, cb) {  },  function (err) {    err ? reject(err) : resolve(res)  })

Async is a library that improves the asynchronous callback process and assigns the asynchronous processing capability to common array processing functions, suchasync.eachIt is equivalent to a multi-threaded version.Array.forEachBut in actual use, do not expect the execution order to be out of order or forward order. The key is the third parameter.

async.each([1, 2, 3], function (item, next) {  console.log(item)  next()}, function (err) {  console.log('all tasks done')})// output://   1//   2//   3//   "all  tasks done"

Generally, becausefs.statIs called asynchronously, soArray.forEachAfter traversing the array, it is difficult to ensure whether all the tasks in it have been completed.Promise.resolve()Data correctness cannot be guaranteed. And passasync.eachThe third parameter of, you can know the status of the task, and ensure that Promise can get the correct data.

path.resolve(opts.folder + '/' + file).replace(process.cwd(), '.')

pathIs a library used to solve directory-related problems.path.resolveWill./dirTo/Users/USER/PATH/TO/dir(Mac) format.

process.cwd()Will return the directory of the process that calls this script; in addition, there is__dirnameIndicates the directory where the script is located:

Suppose there are the following files:

lib/index.jsindex.js
// in lib/index.jsmodule.exports = function () {  return __dirname}
// in index.jsconsole.log(process.cwd()) // '/Users/USER/PATH/'console.log(require('./lib')()) // '/Users/USER/PATH/lib/'

The remaining code is not explained one by one, and the following work is roughly done:

  1. Read all files in the specified directory first (fs.readdir)
  2. Useasync.eachTraverse the obtained results
  3. Determine whether the stat of each file is a directory (fs.stat)
    1. If yes, if the check operator does not meet the condition, the next round of recursion (readFiles)
    2. If no, the check operator does not meet the conditions. If yes, it is added to the final result array (res.push())
  4. Repeat 1-3 until all objects are traversed.

Note that:

  • cb()Appears in multiple places for notificationsasync.eachThis task has been executed
  • Use Promise in RecursionreadFiles.then()First parameter ProcessingresIs returned and usedreadFiles.catchProcessingerror, UsereadFiles.finallyProcessingcb()(Because the async task must be notified to be completed, it should be handled here)
  • Promise.finallyIt is a unique API of bluebird. The native Promise needs to implement it like this:readFiles.then().catch().then(), SecondthenEquivalentfinally(But not intuitive enough)
function formatOptions (opts) {}

The parameters used for formatting are not explained much.

Note thatoptsObject To include all parameters and pass themreadFiles. Because of JavaScript features, it is much easier to pass parameters with a JSON object, for example:

function formatOptions (folder, pattern, ignoreCase, nameOnly, exclude, excludeNameOnly, recusive) {}

Because V8 in Node. js does not contain the default function parameter values, the most convenient method is to use a JSON object:

var options = _.assign({}, {  key_1: default_value_1,  key_2: default_value_2  key_3: default_value_3}, opts)

JSON objects also facilitate expansion-no interface needs to be changed whether to add or delete keys.

Finally,SagaseClass, and create a newSagaseObject:

function Sagase () {  Object.defineProperties(    this,    {      find: {        enumerable: true,        value: function (opts) {          return readFiles(formatOptions(opts))        }      }    }  )}module.exports = new Sagase()

Object.definePropertyAndObject.definePropertiesIt is a new feature added to ECMAScript 5. Through these features, you can create many interesting things, such as the traditionaljQuery.fn.size():

jQuery.fn.size = function () {  return Number.MAX_SAFE_INTEGER}var body = $('body')body.length // 1body.size() // 9007199254740991

Replace it with ES 5:

Object.defineProperties(  jQuery.fn,  {    size: {      enumerable: true,      get: function () {        return this.length      }    }  })var body = $('body')body.length // 1body.size // 1jQuery.fn.size = function () {  return Number.MAX_SAFE_INTEGER}body.size // 1body.size() // TypeError: number is not a function

Rational UseconstAndObject.definePropertyYou can avoid unexpected situations and ensure program robustness.

Slavelib/sagase.jsNode. the asynchronous feature of js makes the function layer by layer ('fs. readdir-> fs. stat-> isDirectory (), which is not easy to write or understand, for example:

function getJSON () {  var json  fs.readFile('demo.json', function (err, buff) {    json = JSON.parse(buff.toString())  })  return json}getJSON() // undefined

Of course, you can also use the synchronous interface in Node. js, suchfs.readdirSync,:

  • Node. js synchronous interfaces are not necessarily more efficient than Python, Ruby, and other languages.
  • Not all interfaces have synchronous versions, suchchild_process.exec

To take advantage of Node. js, you must use Promise and async to write programs correctly. For example, in such a scenario, the browser needs to obtain the data of all the items and gifts in the shopping cart. The common steps are: Find the product data and get the gifts through the product ID to find the promotion rules, calculates the total price and returns the result. These steps can be concatenated by multiple requests to the database in the backend language. In the RESTful API mode, you can also initiate multiple requests and finally splice them in the browser.

What if I add asynchronous processing on the browser and server?

var router = express.Router()router.get('/cart', function (req, res, next) {  async.parallel(    [      // get all products data      function (next) {        request('/api/products', OPTIONS)          .then(function (data) {            next(null, data)          })      },      // get products gifts      function (next) {        async.map(          PRODUCT_LIST,          function (p, cb) {            request('/api/product/:id/gifts', OPTIONS)              .then(function (data) {                cb(null, data)              })          },          function (err, results) {            next(null, results)          }        )      }    ],    function (err, results) {      RESPONSE_BODY = {        products: results[0],        gifts: results[1],        total: calcTotal(results[0])      }      res.send(RESPONSE_BODY)    }  )})
$.ajax('/cart').then(function () {  // handle products and gifts here})

Through asynchronous processing, the browser can use a single request to complete the effects of multiple requests without damaging the RESTful API structure. This is very advantageous for mobile terminals with tight resources and changing network environments. For computer terminals, the interaction response speed and user experience are improved by reducing the request time.

This article focuses on how to bypass Node. js callback function traps using libraries such as Promise and async. The callback function is the most confusing part of Node. js. If you cannot control it, the written Node. js code will be ugly and difficult to maintain.

To make the Code look better, a new feature-Generator was added to ECMAScript 6. Koa has started to use generator to build a project. The next article describes how to use it.


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.