LoginSignup
1
1

More than 1 year has passed since last update.

Node.js/Typescript で async-lock を使って非同期処理を同期処理風に記述

Posted at

Node.js でファイルアクセスや DB アクセスなどの非同期処理を連続して記述しようとすると、非同期処理の callback がネストしてプログラムが読みにくくなってしまいます。
(書く方もつらい。。。)

そこで、async-lock を使って連続する非同期処理を同一のロック変数でロックすることで、ネストを深くせずに同期処理風に記述できないか、試してみました。

非同期処理の記述

ここでは以下のような処理を順次実行する場合を考えます。
1. ファイル1 を読み込み、変数1 に保存
2. ファイル2 を読み込み、変数2 に保存
3. ファイル3 へ変数1 の値を書き込み
4. ファイル4 へ変数2 の値を書き込み

上記の処理内容を readFile、writeFile で callback を記述すると、以下のようにネストが深いプログラムになってしまいます。

fs.readFile(file1, (err, data1) => { // 処理1
  fs.readFile(file2, (err, data2) => { // 処理2
    fs.writeFile(file3, data1, (err) => { // 処理3
      fs.writeFile(file4, data2, (err) => { // 処理4
      });
    });
  });
});

※ファイル入出力に限らず非同期処理の記述方法を検証するため、読み書き処理で readFileSync()、writeFileSync() は使わないものとします。

async-lock による排他制御

async-lock は排他制御を行うためのライブラリです。
https://www.npmjs.com/package/async-lock
サンプルとして、以下のような例が紹介されています。

var AsyncLock = require('async-lock');
var lock = new AsyncLock();

lock.acquire(key, function(done) {
    // async work
    done(err, ret);
}, function(err, ret) {
    // lock released
}, opts);

key がロック変数で、// async work に排他する処理を記述します。

async-lock を利用した非同期処理の記述

例に挙げた4つの非同期処理を逐次実行するようにするため、async-lock で同一のロック変数を使って排他制御することで、各非同期処理を逐次実行するようにプログラムを記述してみます。

import * as fs from 'fs';
import AsyncLock from 'async-lock';

function main() {
  let var_1 = '--';
  let var_2 = '--';

  const lock = new AsyncLock();

  // file1.txt から読み込み
  lock.acquire('sync', (done: any) => {
    const file = 'file1.txt';
    console.log('before read ' + file);
    fs.readFile(file, 'utf-8', (err, data) => {
      if (err) {
        console.log('error: failed to read ' + file);
      } else {
        var_1 = data;
        console.log(var_1);
        console.log('after read ' + file);
      }
      done();
    });
  });

  // file2 から読み込み
  lock.acquire('sync', (done: any) => {
    const file = 'file2.txt';
    console.log('before read ' + file);
    fs.readFile(file, 'utf-8', (err, data) => {
      if (err) {
        console.log('error: failed to ' + file);
      } else {
        var_2 = data;
        console.log(var_2);
        console.log('after read ' + file);
      }
      done();
    });
  });

  // file3 へ書き込み
  lock.acquire('sync', (done: any) => {
    const file = 'file3.txt';
    console.log('before write to ' + file);
    fs.writeFile(file, var_1, (err: any) => {
      if (err) {
        console.log('error: failed to ' + file);
      } else {
        console.log('after write to '+ file);
      }
      done();
    });
  });

  // file4 へ書き込み
  lock.acquire('sync', (done: any) => {
    const file = 'file4.txt';
    console.log('before write to ' + file);
    fs.writeFile(file, var_2, (err: any) => {
      if (err) {
        console.log('error: failed to ' + file);
      } else {
        console.log('after write to ' + file);
      }
      done();
    });
  });

  // 変数の値を出力
  lock.acquire('sync', (done: any) => {
    console.log('var_1');
    console.log(var_1);

    console.log('var_2');
    console.log(var_2);
    done();

    return 0;
  });
}

console.log('before main');
main();
console.log('after main');

上記のプログラムを実行した結果は以下の通りです。(※はコメント)

before main
before read file1.txt       ※file1 から読み込み
after main                  ※★ 
abcdef

after read file1.txt
before read file2.txt       ※file2 から読み込み
read: 123456

after read file2.txt
before write to file3.txt   ※file3 へ書き込み
write: abcdef

after write to file3.txt
before write to file4.txt   ※file4 へ書き込み
after write to file4.txt
var_1: abcdef               ※var_1 の内容

var_2: 123456               ※var_2 の内容

プログラムの実行結果を見てみると、
file1 から読み込み ⇒ file2 から読み込み ⇒ file3 へ書き込み ⇒ file4 へ書き込み
の順で実行されていることがわかります。

しかし、★の after mainmain() の実行後に表示されるメッセージで、自分の意図とは異なり、一連のファイル読み書き処理後に実行されずに、すり抜けてしまいました。

最後に

async-lock を使うことで、一連の非同期処理を同期処理風に記述することができました。
しかし、意図しないすり抜けが発生しているため、調査してみたいと思います。

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1