Help us understand the problem. What is going on with this article?

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

ふ・・ふつくしい・・。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away