かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript Express async function の UnhandledPromiseRejectionWarning にハマる。

Express で簡単な JWT の API を作って実験していた際に middleware を async function に変更したら UnhandledPromiseRejectionWarning が出るようになってハマってしまったのでメモ

UnhandledPromiseRejectionWarning になったコード

// router
const router = require('express').Router();
const verify = require('./verify');

router.get('/', verify, (req, res) => {
  res.send(req.data);
});

module.exports = router;

middleware

// verify.js
const jwt = require('jsonwebtoken');
const { checkTokenValidity } = require('./token');

module.exports = async function(req, res, next) {
  const token = req.header(TOKEN_HEADER);
  if (!token) {
    return res.status(401).send('Access Denied');
  }

  try {
    // 有効期限切れなどで verify ができないと catch に処理が流れる
    const verified = jwt.verify(token, TOKEN_SECRET);
    
    // token を redis に問い合わせて revoke されてないかチェックする
    const isValidity = await checkTokenValidity(token);
    if (!isValidity) {
      throw new Error('Token is revoked');
    }
    
    req.data = verified;
    next();
  } catch(err) {
    res.status(400).send('Invalid Token');
  }
}

token が渡されない場合や、token が有効な時は問題がないのですが、token が有効でなく Invalid Token になる場合に UnhandledPromiseRejectionWarning が発生します。

note. UnhandledPromiseRejectionWarning

UnhandledPromiseRejectionWarning は非同期処理の Promise でエラーが catch できなかった時に発生するエラー

非同期関数の await checkTokenValidity(token) を追加した後から、この warning が発生するようになったので、この関数が悪いのかと思い別途 try 〜 catch で囲ってみたり、コメントアウトしてみたり、Promise.reject() を書いてみたりしても変化がなく、どうやら middleware を async function にした事で warning が発生するようになっていました。

HEADER が 2重に返されていたのが原因だった

長い warning をよく読んでみると ERR_HTTP_HEADERS_SENT という記述があり、どうやら res.send() された後に更に res.send() がされているのがこの warning 発生の直接的な原因となっているようでした。

async function 内では明示的に return する必要がある

middleware が async function でない場合は next() が呼ばれず res.send() してしまえば、その場で処理が終了になっていたものが、async function にしたことで返される値を待つようになってしまったので、res.send() で処理が終了にならず、関数の最後に返される undefinedPromise.resolve() として受け取っていたということでした。

// verify.js
module.exports = async function(req, res, next) {
  // ...
  try {
    // …
  } catch(err) {
    res.status(400).send('Invalid Token');
  }
  // ここで即時 undefined が return される
}

// router
router.get('/', verify, (req, res) => {
  // verify が async function なので、return を待つのでエラーの場合
  // 関数の最後で返される undefined を Promise.resolve として次の res.send() が呼ばれる
  res.send(req.data);
});

async function な middleware で res.send する時は明示的に return をする

明示的に return res.send() をすればOK

// verify.js
module.exports = async function(req, res, next) {
  // ...
  try {
    // …
  } catch(err) {
    // エラーの場合はこれが返されて関数は終了
    return res.status(400).send('Invalid Token');
  }
  // ここに到達することはない
}

// router
router.get('/', verify, (req, res) => {
  // エラーの場合先に res.send() が返されて実行されているので、res.send(req.data) は実行されない
  res.send(req.data);
});

 
単純に async / await の挙動の考慮漏れが原因でした…
小一時間ほどハマってしまった。


[参考]

アイドルタイムプリパラ☆ミュージックコレクション

アイドルタイムプリパラ☆ミュージックコレクション

  • アーティスト:V.A.
  • 発売日: 2018/06/27
  • メディア: CD

プリパラの履修始めました。

JavaScript FPS と BPM でアニメーションさせたい。(requestAnimationFrame)

ゲームとか音楽プレイヤーを作っていて requestAnimationFrame でループ処理をしていたのですが、FPSBPM で処理を実行できるようにしたかったのでやってみてたメモ

requestAnimationFrame 基本のループ

let requestID;
function loop(now = 0) {
  // 処理
  requestID = requestAnimationFrame(loop);
}
// ループ処理の開始
requestID = requestAnimationFrame(loop);
// ループ処理を停止
function stopAnimation() {
  requestAnimationFrame(requestID)
}

FPS

FPS (Frame Per Second) は 1秒間のコマ数で表される
5FPS なら 1秒間に5コマ、50FPS なら 1秒間に50コマを表示する速度
これを JavaScript で実現するには、実行時間を 1秒をFTPで割った数で割った値の実数部分をコマ数とすれば良い。
JavaScript の 1秒は 1000ms で考えるので、次のような計算になる。

const frame = Math.floot( time / ( 1000 / fps ) );

たいてい毎秒 60 回ですが、一般的に多くのブラウザーでは W3C の勧告に従って、ディスプレイのリフレッシュレートに合わせて行われます。ただし、コールバックの確率は、バックグラウンドのタブや隠れた <iframe> では、パフォーマンス向上やバッテリー消費を減らすために低くなるでしょう。
cf. Window.requestAnimationFrame() - Web API | MDN

requestAnimationFrame はおおよそ 60f/s で動作するので同じフレームが何度も呼び出されないようにしてあげればOK

const fps = 30;
let count = 0;
let requestID;
let time = 0;
let preFrame;

function loop(now = 0) {
  let frame = Math.floor((now - time) / (1000 / fps));
  // fps のフレーム数に達したらもう一度 0 フレームから始める場合
  // frame は 0 から始まるので ftp - 1 まで
  if (frame > fps - 1) {
    time = now;
    count += 1;
    frame = 0;
  }
  // 開始直後 frame がマイナスになることがあるので 0 以上を条件にする
  if (frame >= 0 && preFrame !== frame) {
    // フレームごとの処理
    console.log(count, frame + 1);
  }
  
  preFrame = frame;
  requestID = requestAnimationFrame(loop);
}

!function() {
  time = performance.now();
  requestID = requestAnimationFrame(loop);
}();

BPM

BPM (Beats Per Minute) は 1分間に4分音符の拍数で表される
120 BPM なら 60秒間に 4分音符なら 120回、 8分音符なら4分音符2回分なので 120 * 2回、 2分音符なら4分音符の倍の長さなので 120 * 0.5回。 60秒 をこれらの数で割った値が、各音符の時間間隔になる。計算式にすると次のような感じ

const beat = 16; // 演奏する音符
const beatRate = 60 * 1000 / ( bpm * (beat / 4) );
const frame = Math.floor(time / beatRate);

フレーム計算ができれば後は FTP と同じ

const MINUTES = 60 * 1000; 
const BASE_BEAT = 4; // BPMが4分音符換算なので定数的に扱う
const bpm = 120;
const beat = 16; // 実際にカウントするベースになる音符
const beatRate = MINUTES / (bpm * beat / MINUTES);
let section = 1;
let requestID;
let time = 0;
let preBeatCount;

function loop(now = 0) {
  let beatCount = Math.floor((now - time) / beatRate);
  // beatCount (音符の数)が bpm (1章節) に入る数に達したら beatCount を 0 に戻す
  if (beatCount > beat - 1) {
    time = now;
    section += 1;
    beatCount = 0;
  }
  if (beatCount >= 0 && preBeatCount !== beatCount) {
    // beat 音符毎に実行する処理
    console.log(section, beatCount + 1);
  }
  preBeatCount = beatCount;
  requestID = requestAnimationFrame(loop);
}

!function() {
  time = performance.now();
  requestID = requestAnimationFrame(loop);
}();

SAMPLE

See the Pen requestAnimationFrame FTP / BPM by KIKIKI (@kikiki_kiki) on CodePen.

所感

多分超厳密なものではないと思うけど、 FPS / BPM を実現することが出来ました。
FPS / BPM は一定の間隔で処理をするものなので、テトリスとかを作る場合はレベルに応じてフレームが実行される間隔の時間が短くなる計算にすれば良さそう。

今回コレを作るにあたって BPM とか調べてて、テンポ (tempo) がイタリア語だってことを知りました。
そういえば、ドレミってイタリア語からだったので、日本の音楽は伝統的にイタリアの影響が強いのでしょうか。興味深いです。


[参考]

ピクサー流 創造するちから

ピクサー流 創造するちから

ESLint + Prettier で JavaScript と CSS を自動フォーマットするぞ

自動整形してるののメモ

ESLint

install
$ yarn add -D eslint

eslint: ^7.1.0

generate config file

対話式で設定ファイル .eslintrc.js を作成できる

$ yarn eslint --init
? How would you like to use ESLint?
❯ To check syntax and find problems 
? What type of modules does your project use?
❯ JavaScript modules (import/export)
# プロジェクトに合わせて選択
? Which framework does your project use?
  React 
  Vue.js 
❯ None of these
? Does your project use TypeScript? (y/N)
? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Browser
? What format do you want your config file to be in? (Use arrow keys)
❯ JavaScript 

eslint:recommended が設定されたファイルが作成される。
( npm だと npm run eslint --init だえエラーになってしまい $(npm bin)/eslint --init なので yarn の方が楽かも。)

eslint を実行する script を作成

package.json

"scripts": {
  "lint": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'"
}

.eslintrc.js の設定

.eslintrc.js

module.exports = {
    "env": {
        "browser": true,
        "es6": true,
        "node": true, // require を使う時は追加
    },
    "extends": [
      // extends は後から追加した方のルールが優先される
      "eslint:recommended",
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 11,
        "sourceType": "module"
    },
    "rules": {
      // プロジェクトに合わせたルールを設定
      "no-console": ["warn", { "allow": ["warn", "error"] }],
      "semi": ["error", "always"],
    }
};

extends, plugin で厳し目のルールを設定して、rules で部分的に弱めるのが良さそう。

Prettier

install
$ yarn add -D prettier

prettier: ^2.0.5

設定ファイルの作成

Prettier は設定ファイルを自分で作成する必要がある
package.json 内に "prettier": {} で作成することも可能 cf. Configuration File · Prettier

$ touch .prettierrc

適当に設定を作成
.prettierrc

{
  "trailingComma": "es5",
  "tabWidth": 2,
  "singleQuote": true,
  "arrowParens": "always"
}
# Prettier の実行
$ yarn prettier --write './src/**/*.{js,ts,jsx,tsx}'

or npm-script を追加

package.json

"scripts": {
  "lint": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'",
  "format": "prettier --write './src/**/*.{js,ts,jsx,tsx}'"
}

ESLint から Prettier を実行する

パッケージをインスール

$ yarn add -D eslint-config-prettier eslint-plugin-prettier

.eslintrc.js を編集

module.exports = {
    // 略
    "extends": [
      "eslint:recommended",
      "plugin:prettier/recommended", // 追加
    ],
    // 略
};

eslint-plugin-prettier が eslint-config-prettier を使ってよしなにしてくれる。
"plugin:prettier/recommended""eslint:recommended" より後に書くことで Prettier の設定が優先されるようになる。

これで eslint のコマンド実行で同時に Prettier が実行されるようになった。

$ yarn lint:fix
$ eslint --fix './src/**/*.{js,ts,jsx,tsx}'
✨  Done in 0.55s.

package.json の不要な npm-script を削除

"scripts": {
  "lint": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'",
-  "format": "prettier --write './src/**/*.{js,ts,jsx,tsx}'"
}

CSS も Prettier で整形する

stylelint を利用する

パッケージをインストール
$ yarn add -D stylelint stylelint-config-standard stylelint-config-prettier

scss を使う場合は stylelint-scss もインストールする

設定ファイルを作成
$ touch .stylelintrc.json

.stylelintrc.json

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-prettier" // 下に書いたほうが優先される
  ],
  "plugins": [
    "stylelint-scss" // stylelint-scss はプラグイン
  ],
  "rules": {
    // 独自のルールを指定
    "string-quotes": "single"
  }
}
Prettier 経由で stylelint を呼び出してフォーマットする

Prettier 経由で stylelint を呼び出してフォーマットするためのパッケージをインストール

prettier-stylelint と forkして作られた prettier-stylelint-formatter がメジャーっぽい。 (リポジトリの最終更新結構前なのが気になるけど…)
prettier-stylelint だと何故かうまくフォーマットできなかったので prettier-stylelint-formatter を使うことにしました。

$ yarn add -D prettier-stylelint-formatter

package.json

"scripts": {
  "lint": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'",
  // 下記を追加
  "lint:css": "prettier-stylelint-formatter 'src/**/*.{css,scss}'",
  "lint:css:fix": "prettier-stylelint-formatter --quiet --write 'src/**/*.{css,scss}'"
}

実行

$ yarn lint:css:fix
$ prettier-stylelint-formatter --quiet --write 'src/**/*.{css,scss}'
✨  Done in 1.93s.

これで CSS もフォーマットできるようになりました (ง ˙˘˙ )ว ヤッタネ

JS と CSS のフォーマットを同時に実行できるようにする。

コマンドが別々だとめんどくさいので。
npm-script を同時に実行できるパッケージをインストール

$ yarn add -D npm-run-all

package.json npm-script を編集。

"scripts": {
  "format": "run-p lint:js:fix lint:css:fix",
  "lint:js": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:js:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'",
  "lint:css": "prettier-stylelint-formatter 'src/**/*.{css,scss}'",
  "lint:css:fix": "prettier-stylelint-formatter --quiet --write 'src/**/*.{css,scss}'"
}

JS ファイルと CSS ファイルなので run-p で並行処理にしました。

$ yarn format
$ run-p lint:js:fix lint:css:fix
$ eslint --fix './src/**/*.{js,ts,jsx,tsx}'
$ prettier-stylelint-formatter --quiet --write 'src/**/*.{css,scss}'
✨  Done in 3.68s.

A W E S O M E (ˊo̴̶̷̤ ᴗ o̴̶̷̤ˋ)

おまけ git commit 時に自動フォーマットする

huskylint-staged を使えばコマンドから git commit する際に自動的に lint やフォーマットを実行できる GUIでのコミットはダメな場合があるっぽい

# install
$ yarn add -D husky lint-staged
# 設定

package.json

"scripts": {
  "format": "run-p lint:js:fix lint:css:fix",
  "lint:js": "eslint './src/**/*.{js,ts,jsx,tsx}'",
  "lint:js:fix": "eslint --fix './src/**/*.{js,ts,jsx,tsx}'",
  "lint:css": "prettier-stylelint-formatter 'src/**/*.{css,scss}'",
  "lint:css:fix": "prettier-stylelint-formatter --quiet --write 'src/**/*.{css,scss}'"
},
"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "*.{js,jsx}": "npm run lint:js:fix",
  "*.{css,scss}": "npm run lint:css:fix"
},

pre-commit をトリガーに lint-staged を実行、ファイルが *.{js,jsx} なら npm run lint:js:fix*.{css,scss} なら npm run lint:css:fix をそれぞれ実行する。
コミット時に lint と フォーマットがかかるのでコミット完了に少し時間がかかるけど、フォーマット忘れがちな方は良いかも。
プレコミットでのフォーマットを入れるなら npm-run-all の設定は不要と思います。

 
おわり。
自分用メモに振り返りながら書いてたら思った以上に時間かかってしまった…


[参考]

木になる。