JavaSctipt 特有の非同期処理でのメモです。
問題点
以下の処理はローカルファイルの json 形式のデータをパースして受け取るものです。 その json ファイルをタイトルが入っています。
// data/products.json
[{"title":"hoge"},{"title":"hgoe"}]
// models
const fs = require("fs");
const path = require("path");
module.exports = class Product {
constructor(t) {
this.title = t;
}
static fetchAll(cb) {
const p = path.join(
path.dirname(process.mainModule.filename),
"data",
"products.json"
);
fs.readFile(p, (err, fileContent) => {
if (err) {
return [];
}
return JSON.parse(fileContent);
});
}
};
これの JSON を controllers で受け取り、view にデータを渡します。
// controllers
exports.getProducts = (req, res, next) => {
Products.fetchAll(products => {
res.render("shop", {
prods: products,
pageTitle: "Shop",
path: "/",
hasProducts: products.length > 0,
activeShop: true,
productCSS: true
});
});
};
ただし、これのコードでは json を受け取れず undefined になります。 問題は以下のコードです。
static fetchAll() {
const p = path.join(
path.dirname(process.mainModule.filename),
"data",
"products.json"
);
// ①:ここが非同期処理なので処理が終わる前に通過してundefinedになる
fs.readFile(p, (err, fileContent) => {
// 内部の条件で値を返すが、fetchAll関数自体が値を返すわけではない。
if (err) {
return [];
}
return JSON.parse(fileContent);
});
}
データを読み込む処理①は条件内で値を返しますが、関数自体が値を返している訳ではありません。node.js では非同期通信なので①が終了して関数が値を返す前に、controller が view にデータを渡す処理が実行されてしまいます。
解決案
これを解決するための方法はいろいろあります。
- callback
- async/await Promise
callback
fetchAll の引数に callback 関数を受け取る方法です。
// callback で受け取る
static fetchAll(cb) {
const p = path.join(
path.dirname(process.mainModule.filename),
"data",
"products.json"
);
fs.readFile(p, (err, fileContent) => {
// 内部の条件で値を返すが、fetchAll() 自体が値を返すわけではない。
if (err) {
return cb([]);
}
return cb(JSON.parse(fileContent));
});
}
async/await
Promise の async/await を使った方法です。これを使うと非同期処理を同期処理っぽく書くことができます。
// models
static async fetchAll() {
const p = path.join(rootDir, "data", "products.json");
// await で処理が終わるまで待ってもらう
const data = await fs.readFile(p);
return JSON.parse(data);
}
// controller
exports.getProducts = async (req, res) => {
const products = await Product.fetchAll();
res.render('shop', { prods: products, docTitle: 'Shop', path: '/' });
}
可読性が高いので、Nodejs のバージョンが対応していれば現在はこちらを使います。
所感
非同期処理は他の同期的な言語にない仕様であり、なかなか理解が難しいです。 また知見がまとまったら、まとめます。