Edited at

【Promise?】そろそろ調べるか・・【async/await!?】

More than 1 year has passed since last update.


時代についていけてない俺

非同期処理を書くときに、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まとめ

参考1

参考2

質問など
答え

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にする
→直列にかける!


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));

ふ・・ふつくしい・・。