Node.js 101(2): Promise and async
9685 ワード
——原文住所:http://blog.chrisyip.im/nodejs-101-package-promise-and-async
まずSagaseのプロジェクト構造を振り返ってみましょう.
前回は
コードが長いので、一節ずつ話して、完全にGitHubを開いてみましょう.
コンパイラがstrict modeに入ることを通知し、主な役割はコンパイラにいくつかのピットをチェックさせることであり、経験のあるJavaScript開発者は無視することができます.
すべての依存項目を参照し、
Promiseオブジェクトを返します.
なぜならjsの特徴は非同期であり、一般的に非同期で処理する必要がある.
1つの関数で多くのことを処理する必要がある場合、この関数の戻り結果を複数のファイルで使用できるようにする必要がある場合、コールバックだけでは骨が折れる--どのファイルがいつその戻り結果を使用する必要があるか分からない.
Promiseを使うと、簡単になります.
Promiseのパッケージ化により、
ここでbluebirdというサードパーティ製Promiseライブラリを導入しましたNode.jsバージョンが
asyncは、非同期コールバックプロセスを改善し、非同期処理能力を通常の配列処理関数に付与するライブラリであり、例えば
一般に、
次のファイルがあるとします.
残りのコードは一つ一つ説明せず、大体以下の仕事をしました.
指定ディレクトリのすべてのファイルを先に読み込む(
各ファイルのstatがディレクトリであるか否かを判断する(
もし、チェックマークが条件を満たしていなければ、次のラウンド再帰(
いいえ、チェックマークが条件を満たしていない場合は、最終結果の配列(
すべてのファイルを巡回するまで1~3を繰り返します.
次の点に注意してください.
再帰的にはPromise
入力パラメータをフォーマットするには、あまり説明しません.
ここでは、
なぜならjs用のV 8はまだ関数パラメータのデフォルト値を含んでいないので、最も便利な方法はJSONオブジェクトを使うことです.
さらにJSONオブジェクトは拡張にも役立ちます.keyを追加しても削除しても、インタフェースを変更する必要はありません.
最後に、
ES 5の書き方に変えます.
もちろんノードでjsでは、
Node.js同期インタフェースはPython、Rubyなどの言語よりも効率的であるとは限らないすべてのインタフェースに同期バージョンがあるわけではありません.
発揮するためにjsの利点は,Promise,asyncを正しく利用してプログラムを記述する必要がある.例えば、ブラウザ側がカート内のすべての商品、景品のデータを取得する必要があるシーンがあります.よくあるステップは、商品データを探して、商品IDで販促ルールを探して景品を得て、総価格を計算して、結果を返すことです.これらのステップは、データベースに最後にバックエンド言語で接続するように複数回要求することができる.RESTful APIモードの場合は、複数のリクエストを開始し、最後にブラウザ側で接続することもできます.
ブラウザ側とサーバ側で非同期処理を加えるとしたら?
非同期の処理により,ブラウザは1回の要求で複数回の要求の効果を達成することができ,RESTful APIの構造を破壊することはない.これは資源が緊張し、ネットワーク環境が変化するモバイル端末にとって、非常に有利である.一方,コンピュータ側ではリクエスト時間を短縮することでインタラクティブな応答速度を向上させ,ユーザ体験を向上させる.
この主な内容はPromise、asyncなどのライブラリをどのように利用してNodeを迂回するかです.jsのコールバック関数ピット.コールバック関数はNodeです.jsが一番黒いところは、それをコントロールできないなら、書いたNode.jsコードはかなり醜く、メンテナンスしにくいです.
コードをきれいにするために、ECMAScript 6には新しい特性であるGeneratorが追加されています.Koaはすでにgeneratorを使ってプロジェクトを構築し始めていますが、具体的にはどう使うか、次の編で話しましょう.
画像認識図のQRコードを長く押す(または、微信の公衆番号FrontEndStoryを検索する)ことで、「フロントエンドのこと」に注目し、最新のフロントエンド技術を知ることができます.
まず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
処理error
、readFiles.finally
処理cb()
(asyncタスクが完了したことを通知する必要があるため、ここでは一括処理)を用いる.Promise.finally
はbluebird特有のAPIであり、原生Promiseはこのように実現する必要がある:readFiles.then().catch().then()
、2番目のthen
はfinally
に相当する(ただし直感的ではない)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.defineProperty
とObject.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
const
とObject.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を検索する)ことで、「フロントエンドのこと」に注目し、最新のフロントエンド技術を知ることができます.