Node.js 101(2): Promise and async

9685 ワード

——原文住所:http://blog.chrisyip.im/nodejs-101-package-promise-and-async
まずSagaseのプロジェクト構造を振り返ってみましょう.
lib/
    cli.js
    sagase.js
Gruntfile.js
package.json

前回はpackage.json、このlib/sagase.jsについて話しました.
コードが長いので、一節ずつ話して、完全にGitHubを開いてみましょう.
'use strict';

コンパイラがstrict modeに入ることを通知し、主な役割はコンパイラにいくつかのピットをチェックさせることであり、経験のあるJavaScript開発者は無視することができます.
var fs = require('fs-extra'),
    async = require('async'),
    _ = require('lodash'),
    Promise = require('bluebird'),
    path = require('path')

すべての依存項目を参照し、--harmonyを使用する場合は、constキーワードの代わりにvarを使用することを推奨し、変数の変更を回避します.
var mar = 'March'
mar = 'June'
console.log(mar) // 'June'

const july = 'July'
july = 'May'
console.log(july) // 'July'
function readFiles (opts) {}には多くの情報が含まれています.
return new Promise(function (resolve, reject) {}

Promiseオブジェクトを返します.
なぜならjsの特徴は非同期であり、一般的に非同期で処理する必要がある.
// get all files in DIR
fs.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
        })
      }
    })
  })
})

1つの関数で多くのことを処理する必要がある場合、この関数の戻り結果を複数のファイルで使用できるようにする必要がある場合、コールバックだけでは骨が折れる--どのファイルがいつその戻り結果を使用する必要があるか分からない.
Promiseを使うと、簡単になります.
// in a.js
var Promise = require('bluebird'),
    readFile

module.exports = new Promise(function (resolve, reject) {
  fs.readdir(DIR, function (err, files) {
    err ? reject(err) : resolve(files)
  })
})
// in b.js
var 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.js
var readFile = require('./a.js')

readFile.then(function (files) {
  // the files still exist and accessable
})

Promiseのパッケージ化により、resolveの結果を異なるファイルにまたがることができ、1つのコールバック関数ですべてのことを処理する必要はありません.また、.then()の2番目の関数がrejectのエラー結果を処理することにより、多重判定が回避される.
ここでbluebirdというサードパーティ製Promiseライブラリを導入しましたNode.jsバージョンが>= 0.11.13であれば、サードパーティライブラリを導入する必要はありません.
async.each(
  files,
  function (file, cb) {
  },
  function (err) {
    err ? reject(err) : resolve(res)
  }
)

asyncは、非同期コールバックプロセスを改善し、非同期処理能力を通常の配列処理関数に付与するライブラリであり、例えばasync.eachはマルチスレッド版のArray.forEachに相当するが、実際の使用では、実行順序が乱順または正順であることは期待されず、3番目のパラメータが重要である.
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"

一般に、fs.statは非同期呼び出しであるため、Array.forEachが配列を巡回した後、中のタスクがすべて完了したか否かを保証することは困難であり、このときPromise.resolve()を呼び出すとデータの正確性を保証することはできない.async.eachの3番目のパラメータでは、タスクの状態がわかり、Promiseが正しいデータを得ることができることを保証します.
path.resolve(opts.folder + '/' + file).replace(process.cwd(), '.')
pathは、ディレクトリに関連する問題を解決するためのライブラリです.path.resolveは、./dir/Users/USER/PATH/TO/dir(Mac)形式の完全なディレクトリに変換します.process.cwd()は、このスクリプトを呼び出すプロセスが存在するディレクトリを返します.また、__dirnameは、スクリプトが存在するディレクトリを指します.
次のファイルがあるとします.
lib/index.js
index.js
// in lib/index.js
module.exports = function () {
  return __dirname
}
// in index.js
console.log(process.cwd()) // '/Users/USER/PATH/'

console.log(require('./lib')()) // '/Users/USER/PATH/lib/'

残りのコードは一つ一つ説明せず、大体以下の仕事をしました.
指定ディレクトリのすべてのファイルを先に読み込む(fs.readdir).async.eachを使用して取得する結果.
各ファイルのstatがディレクトリであるか否かを判断する(fs.stat)
もし、チェックマークが条件を満たしていなければ、次のラウンド再帰(readFiles)に進む.
いいえ、チェックマークが条件を満たしていない場合は、最終結果の配列(res.push())に追加します.

すべてのファイルを巡回するまで1~3を繰り返します.
次の点に注意してください.cb()は、async.eachにこのタスクが実行済みであることを通知するために複数の場所で現れる.
再帰的にはPromise readFiles.then()の第1パラメータ処理resの戻り、readFiles.catch処理errorreadFiles.finally処理cb()(asyncタスクが完了したことを通知する必要があるため、ここでは一括処理)を用いる.Promise.finallyはbluebird特有のAPIであり、原生Promiseはこのように実現する必要がある:readFiles.then().catch().then()、2番目のthenfinallyに相当する(ただし直感的ではない)
function formatOptions (opts) {}

入力パラメータをフォーマットするには、あまり説明しません.
ここでは、optsオブジェクトがすべてのパラメータを含み、readFilesに渡されることに注意されたい.JavaScriptの機能により、JSONオブジェクトでパラメータを渡すと便利です.たとえば、次のようになります.
function formatOptions (folder, pattern, ignoreCase, nameOnly, exclude, excludeNameOnly, recusive) {}

なぜならjs用のV 8はまだ関数パラメータのデフォルト値を含んでいないので、最も便利な方法はJSONオブジェクトを使うことです.
var options = _.assign({}, {
  key_1: default_value_1,
  key_2: default_value_2
  key_3: default_value_3
}, opts)

さらにJSONオブジェクトは拡張にも役立ちます.keyを追加しても削除しても、インタフェースを変更する必要はありません.
最後に、Sagaseクラスを定義し、このクラスを外部で呼び出すと、新しいSagaseオブジェクトが作成されます.
function Sagase () {
  Object.defineProperties(
    this,
    {
      find: {
        enumerable: true,
        value: function (opts) {
          return readFiles(formatOptions(opts))
        }
      }
    }
  )
}

module.exports = new Sagase()
Object.definePropertyObject.definePropertiesはECMAScript 5に新しく加わった特性で、伝統的なjQuery.fn.size()のような多くの面白いものを作成することができます.
jQuery.fn.size = function () {
  return Number.MAX_SAFE_INTEGER
}

var body = $('body')
body.length // 1
body.size() // 9007199254740991

ES 5の書き方に変えます.
Object.defineProperties(
  jQuery.fn,
  {
    size: {
      enumerable: true,
      get: function () {
        return this.length
      }
    }
  }
)

var body = $('body')
body.length // 1
body.size // 1

jQuery.fn.size = function () {
  return Number.MAX_SAFE_INTEGER
}

body.size // 1
body.size() // TypeError: number is not a function
constObject.definePropertyを合理的に利用することは、プログラムの堅牢性を保証するために、いくつかの予想外の状況を避けることができる.lib/sagase.jsのコードから分かるように、Node.jsの非同期特性により、関数は一連の層(`fs.readdir->fs.stat->isDirectory())であり、書くと面白くないし、理解にも不利です.例えば、
function getJSON () {
  var json
  fs.readFile('demo.json', function (err, buff) {
    json = JSON.parse(buff.toString())
  })
  return json
}

getJSON() // undefined

もちろんノードでjsでは、fs.readdirSyncなどの同期インタフェースを使用することもできますが、
Node.js同期インタフェースはPython、Rubyなどの言語よりも効率的であるとは限らないすべてのインタフェースに同期バージョンがあるわけではありません.child_process.exec などです.
発揮するためにjsの利点は,Promise,asyncを正しく利用してプログラムを記述する必要がある.例えば、ブラウザ側がカート内のすべての商品、景品のデータを取得する必要があるシーンがあります.よくあるステップは、商品データを探して、商品IDで販促ルールを探して景品を得て、総価格を計算して、結果を返すことです.これらのステップは、データベースに最後にバックエンド言語で接続するように複数回要求することができる.RESTful APIモードの場合は、複数のリクエストを開始し、最後にブラウザ側で接続することもできます.
ブラウザ側とサーバ側で非同期処理を加えるとしたら?
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
})

非同期の処理により,ブラウザは1回の要求で複数回の要求の効果を達成することができ,RESTful APIの構造を破壊することはない.これは資源が緊張し、ネットワーク環境が変化するモバイル端末にとって、非常に有利である.一方,コンピュータ側ではリクエスト時間を短縮することでインタラクティブな応答速度を向上させ,ユーザ体験を向上させる.
この主な内容はPromise、asyncなどのライブラリをどのように利用してNodeを迂回するかです.jsのコールバック関数ピット.コールバック関数はNodeです.jsが一番黒いところは、それをコントロールできないなら、書いたNode.jsコードはかなり醜く、メンテナンスしにくいです.
コードをきれいにするために、ECMAScript 6には新しい特性であるGeneratorが追加されています.Koaはすでにgeneratorを使ってプロジェクトを構築し始めていますが、具体的にはどう使うか、次の編で話しましょう.
画像認識図のQRコードを長く押す(または、微信の公衆番号FrontEndStoryを検索する)ことで、「フロントエンドのこと」に注目し、最新のフロントエンド技術を知ることができます.
Node.js 101(2): Promise and async_第1张图片