時代についていけてない俺
非同期処理を書くときに、asyncモジュールを使っていた。
Promiseの話は聞いたことくらいはあった。
(僕の大好きなasyncモジュールはPromiseを使っているのかしら・・?)
いつの間にか、時代はasync/awaitだよ!
みたいなことになっていた。
そろそろ我も、基礎知識くらいはつけねば。
さらっと知りたい人も是非ご参考に。
※詳細までは説明してないので、さらなる知識をお求めの方はもっと良記事に行ってください。
PureなJSで書く
ファイルの読み込みを行なって、
そこに記載されているファイルをさらに読み込む。
というコールバック地獄一歩手前のコード。
fs.readFile('./next.txt', 'utf8', (err, nextFile) => {
if ( err ) return console.log(err.message);
fs.readFile(nextFile.trim(), 'utf8', (err, message) => {
if ( err ) return console.log(err.message);
console.log('Pure: ' + message);
});
});
asyncモジュールで書く
asyncモジュールを用いて、書き直したもの。
waterfallの第1引数のリストにメソッドを連ねていくことで、
直列っぽく書ける。
ホッとする。
async.waterfall([
done => {
fs.readFile('./next.txt', 'utf8', (err, nextFile) => {
if ( err ) return done(err);
done(null, nextFile);
});
},
(nextFile, done) => {
fs.readFile(nextFile.trim(), 'utf8', (err, message) => {
if ( err ) return done(err);
done(null, message);
});
}
], (err, message) => {
if ( err ) return console.log(err.message);
console.log('async: ' + message);
});
Promise
Promiseまとめ
質問など | 答え |
---|---|
Promiseって何? | Promiseは非同期処理の完了処理とその結果を表現する →完了処理や結果を直列にかければ、callback地獄が避けられる! |
完了処理?結果? | ・Promiseは状態を持っている ・状態を変更する処理やその処理の結果のこと (初期状態:pending、完了:fulfilled、失敗:rejected) |
どうやって完了処理を書くの? | thenメソッドというのがあり、完了と失敗時のハンドラを登録できる |
let p = new Promise((resolve, reject) => {
return resolve(1); // fulfilledなPromise{ <1> } になる
});
// 上記のresolveが、下記のcallbackになる
p.then((val) => console.log(val)); // 1
質問など | 答え |
---|---|
二つ分の非同期関数しか 書けないジャマイカ |
基礎知識 ・thenメソッドはPromiseを返す ・thenメソッドが返すPromiseは、thenに指定したハンドラの戻り値 →Promiseをreturnした場合: そのPromiseをthenは返す →値をreturnした場合: その値をセットした、fulfilledなPromiseを返す ・↑のthenの特性をいかすと、メソッドチェーンが可能であり、コールバック地獄を同期的に書ける |
Promise.resolveとか静的関数があるのだが? | Promise.resolve(val)は、fulfilledなPromiseのシンタックスシュガー |
Promiseで書いてみた
見た目はasync.jsに似ているかも。
new Promiseがぱっと見、邪魔に見える。
でもわざわざ関数定義してそれを呼び出すのも・・。
Promise.resolve()
.then(() => new Promise((resolve, reject) => {
fs.readFile('./next.txt', 'utf8', (err, nextFile) => {
if ( err ) return reject(err);
return resolve(nextFile);
});
}))
.then(nextFile => new Promise((resolve, reject) => {
fs.readFile(nextFile.trim(), 'utf8', (err, message) => {
if ( err ) return reject(err);
return resolve(message);
});
}))
.then(message => {
console.log('Promise: ' + message);
})
.catch((err) => {
console.log(err.message);
});
個人的につまづきポイントだったのが、
thenで指定したcallback(onFulfilled | onRejected)の処理に非同期関数を用いた場合、
非同期関数のcallbackでreturnした結果がPromiseに入ると思い込んでた件。
言ってて混乱するので下記参照。
.then(() => {
fs.readFile('./next.txt', 'utf8', (err, nextFile) => {
// ここでthenのreturnがPromise{ <nextFile> }になると思い込んでしまった
return nextFile;
});
});
考えたらすぐわかることだが、上記のreturnはcallbackの戻り値であり、
thenの戻り値ではない。
onFulfilledの戻り値は上記だとundefinedだし、これだとnextFileを
thenの戻り値とすることができない。
結果、Promise化するしかなくなる。
(ES2017)async/await
async/awaitまとめ
質問など | 答え |
---|---|
asyncって何さ | 関数名にasyncをつけると、Promiseを返す関数となる |
asyncつけるとどうなる? | ・returnすると、その値を保持したfulfilledなPromiseを返す ・asyncFunctionでthrowすると、その値を保持したrejectedなPromiseを返す |
let af = async () => 1;
console.log(af()); // Promise{ 1 }
af().then((val) => console.log(val)); // 1
質問など | 答え |
---|---|
awaitって何さ | ・awaitは、asyncFunction内で使える ・Promiseを返す関数呼び出しの前にawaitをつけると、Promiseが解決するまで待ってくれる |
何が便利? | awaitが指定された関数の戻り値はPromiseではなく、 Promiseが完了した結果の値を返す →欲しい値を保持するPromiseを戻り値とする関数を作る →awaitにする →直列にかける! |
(async () => {
let noAwait = new Promise((resolve) => resolve(10));
let awaitTest = await new Promise((resolve) => resolve(10));
console.log(noAwait); // Promise{ 10 }
console.log(awaitTest); // 10
})();
async/awaitで書いてみた
即時関数で書いたせいか、括弧がしゅごい。
ただ、そこを無視すれば、とても人間には読みやすくなりますね!
あと相変わらずnew Promiseが邪魔。
(async () => {
let nextFile = await(() => new Promise((resolve, reject) => {
fs.readFile('./next.txt', 'utf8', (err, nextFile) => {
if ( err ) return reject(err);
resolve(nextFile);
});
}))();
let message = await(() => new Promise((resolve, reject) => {
fs.readFile(nextFile.trim(), 'utf8', (err, message) => {
if ( err ) return reject(err);
return resolve(message);
});
}))();
console.log('async/await:', message);
})()
.catch((err) => console.log(err.message));
new Promiseが邪魔な件
riawiththesamさんから頂いたコメントより。
callbackスタイルの関数をPromise化してくれる「promisify」というものがあります。
bluebird、es6-promisifyといったモジュールで実現できるほか、
Node.js8にはutilに追加されているそうです。
const readFileAsync = require('util').promisify(fs.readFile);
(async () => {
let nextFile = await readFileAsync('./next.txt', 'utf8');
let message = await readFileAsync(nextFile.trim(), 'utf8');
console.log('async/await:', message);
})()
.catch((err) => console.log(err.message));
ふ・・ふつくしい・・。