' P '

whatever I will forget

JavaScript Promiseオブジェクトと非同期/同期処理(async, await)

Node.jsでは基本非同期処理が主になっている。
同期処理を行いたい場合もあるけど重い処理を待たせて同期処理を敢えて行う必要はないので、
例えば、データベースから情報を取得する、APIをコールするなどは非同期でやるべき。

  • 重い処理は非同期にしてその内に違う処理をやらしておく
    上記が鉄板になるはず

でも非同期処理というのは順番が保証されているわけではない。
非同期処理を処理を待ってからあんなことやりたい、こんなことやりたい、ってなった場合どうしたらいいの?

どうしたらいいの

Promiseオブジェクトというものを使います。

非同期に実行される未来の結果を返すオブジェクト

らしいです。ちなみにES6から登場しました。

従来のやり方

callback()関数を使用する

function doSomething(msg, callback){ 
  setTimeout(
    function () {
      console.log(msg);
      callback();
    }, 
    1000);
}
doSomething("1st call", function() {});

ES6以降のやり方(Promiseを使用する)

function doSomething(msg){ 
  return new Promise(
    function (resolve, reject) {
      setTimeout(
        function () {
          console.log(msg);
          resolve();
        }, 
        1000);
    }); 
}
    
doSomething("1st Call")
  .then(function() {
    return doSomething("2nd Call");
  })
  .then(function() {
    return doSomething("3rd Call");
});    

.thenを使用することで、"1st call"の処理が完了してからdoSomethingの処理を行える。

arrowを使ってさらに読みやすく

function doSomething(msg){ 
  return new Promise((resolve, reject) => {
      setTimeout(
        () => {
          console.log(msg);
          resolve();
        }, 
        1000);
    }) 
}
    
doSomething("1st Call")
  .then(() => doSomething("2nd Call"))
  .then(() => doSomething("3rd Call"));  

実装手順

  1. Promiseオブジェクトのコンストラクタに行いたい処理内容(関数)を記述します
  2. resolve()が呼び出されれば、Promiseは終了となります
  3. promiseに対するthen()が呼び出されれば、Promiseは終了後、then()に記述された内容が処理されます
function doSomething(msg){ 
  return new Promise((resolve, reject) => {
      setTimeout(
        () => {
          try {
            throw new Error('bad error');
            console.log(msg);
            resolve();
          } catch(e) {
            reject(e);
          }
        }, 
        1000);
    }) 
}
    
doSomething("1st Call")
  .then(() => doSomething("2nd Call"))
  .then(() => doSomething("3rd Call"))
  .catch(err => console.error(err.message));  

上記はPromiseでいきなりエラーを発生させているので、bad errorとしか表示されないが、
本来であれば処理でエラーが出た際にcatchに処理させるようにしておく。

さらにサンプルコード

理解するためには色んなコードがあったよいほうがよいはず。。。

基本的な文法として、

new Promise((resolve) => {
    doSomething();
    resolve();
});

である。
resolve、すなわちPromiseが成功した際の値を持って次の処理に行かせたい場合は下記となる.

'use strict';

new Promise((resolve) => {
    const p1Text = 'a';
    resolve(p1Text);
  }).then((promise1) => {
    // promise1 は 'a'のtext。変数名はなんでもよい。
    new Promise((resolve) => {
      const p2Text = 'b';
      resolve(p2Text);
    }).then((promise2) => {
      // promise2 は 'b'のtext
      new Promise((resolve) => {
        const result = `${promise1} + ${promise2}`;
        resolve(result);
      }).then((promise3) => {
        // promise3 は 'ab'
        console.log(promise3); // 'ab'
      });
    });
  });

しかし、.then()に渡すpromise1などの変数は、実はPromiseに対してreturnされるものとなるため、

const foo = getSomething();
return new Promise((resolve) => {
     resolve(foo);
}):

という書き方もできる.

new Promise((resolve) => {
    const p1Text = 'a';
    resolve(p1Text);
  }).then((promise1) => {
    // promise1 は 'a'のtext。変数名はなんでもよい。
    const p2Text = 'b';
    return new Promise((resolve) => {
    resolve(p2Text);
    }).then((promise2) => {
      // promise2 は 'b'のtext
      const result = `${promise1} + ${promise2}`;      
      return new Promise((resolve) => {
        resolve(result);
      }).then((promise3) => {
        // promise3 は 'ab'
        console.log(promise3); // 'ab'
      });
    });
  });

サンプルコード

最近コールバック関数ではなく利用が増えてきたと言われるasync/awaitをとりあえず使ってみます
new Promise((resolve) => { はほぼお決まり作法として覚えておけばよいです。
resolveって関数をPromiseのobject生成時に渡さないといけないようです。(rejectもお決まりっぽいけど省略)
fs.appendfile()がPromiseオブジェクトのコンストラクタで実行されます。その後のコールバック関数の中で、resolve() が実行されて処理終了となります

とりあえず、重要なのは下記のような気がする

  • awaitはPromiseを同期的に処理するように見せかける
  • awaitが指定された処理はその処理が完了するまで次の処理は開始しない
  • awaitasync関数内でしか使用できない
'use strict';
const fs = require('fs');
const fileName = './test.txt';
function appendFileSyncPromise(filename, str) {
    return new Promise((resolve) => {
        fs.appendFile(filename, str, 'utf-8', () => resolve());
    });
}

async function main() {
    for (let count = 0; count < 30; count++) {
        await appendFileSyncPromise(fileName, 'おおおお\n');
        await appendFileSyncPromise(fileName, 'ああああ\n');
        await appendFileSyncPromise(fileName, 'うううう\n');
    }
}

main();

まだまだ頭が混乱してますがとりあえず現時点の理解をめも.

超参考にしたい

sbfl.net

sbfl.net