htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

Promise を再帰させるとどうなるか

ただの興味本位です。

以下のコードを各環境で動かしていきます。

const p = n => new Promise(resolve => { console.log(n); resolve(p(n + 1)); });
p(1);

なるべく公平を期すため、ブラウザの場合は about:blank にて実行します。

環境

PC1:

種別 詳細
OS Windows 10
CPU AMD Rizen 5 1600
MEM DDR4 PC4-24000 16GB x2 (空き 20000MB程度)

PC2:

種別 詳細
OS macOS 10.14.6 (Mojave)
CPU Intel Core i5 5250U
MEM LPDDR3 PC3-12800 4GB (空き1000MB程度)

結果

環境 再帰回数 備考
Edge 44 1000 20回置きにウェイトが入る。終了時は特に出力なし
Safari 3674 コンソールにはエラーが表示されず、
ブラウザページにポップアップでMaximum call stack size exceeded.と表示され終了
Firefox 69 (PC1) 582 too much recursion の例外で終了
Firefox 69 (PC2) 2946 too much recursion の例外で終了
Chrome 77 (PC1) 1934 RangeError: Maximum call stack size exceeded の例外で終了
Chrome 77 (PC2) 1929 RangeError: Maximum call stack size exceeded の例外で終了
node 10.16.3 (PC1) 1818 エラーを吐かずにクラッシュして終了
node 10.16.3 (PC2) 1847 RangeError: Maximum call stack size exceeded の例外で終了
node 12.11.1 (PC1) 1806 エラーを吐かずにクラッシュして終了
node 12.11.1 (PC2) 1835 RangeError: Maximum call stack size exceeded の例外で終了

この規則性のない結果から、おそらく Promise における再帰については厳密な仕様はなく実装依存であることが推定されますが、仕様書のどの辺りを見ればよいのか分からず…。
同じバージョンでも環境によって結果が異なるので、メモリ等の状態によっても変わるのかなと思いますが、ブラウザのソースコード読む気力もなく、「とりあえず Promise の再帰はなるべく避けよう」という辺りで落ち着けておきます。

URLからトラッキングのアレを削除するやつ書いた

モチベーションは Neat URL がだいぶ前から動かなくなっていた*1から。
似たようなアドオンも軒並み死んでいるように思うので自分で作りました。

とりあえず単純なトラッキングタグ(って言うのかな?)は削除できるようにしてあり、最終的な目標としては Neat URL と同等の機能を持たせてあげたい。

グリモンスクリプトなので、 Edge, Firefox, Chrome, Safari 等のグリモン系アドオンがあり

'URL' in window && 'URLSearchParams' in window

が真となる環境であれば問題なく使えるはずです。
なんなら Firefox for Android であればスマートフォンでも使えます*2

インストール

gist.github.com

*1:Issueにそれらしいのがないので、他のアドオンとの干渉によるおま環かもしれません。

*2:執筆時点では証明書問題で動きませんが、じきに回復します。

npm scripts で OS ごとに処理を分ける

npm scriptsclean ってコマンドを登録しようとしたんですよ。

普通なら rimraf を使うところなんですが、 cleanの過程で node_modules ディレクトリも削除して綺麗にしたい。
かと言って npm i -g rimraf など、環境依存になるので以ての外。

rm コマンドは *NIX 環境に依存してしまう。
rd コマンドは Windows環境に依存してしまう。

さて困った。

そこで閃きました。

"scripts": {
  "clean": "node -e \"process.exit(process.platform === 'win32' ^ 1)\" && rd /s /q node_modules || rm -rf node_modules"
}

こうします。

これならば、process.platform を評価して win32 だった場合とそれ以外で処理を分けることができます。

true ^ 1 // ==> 0 false ^ 1 // ==> 1 となり、シェルはこれをエラーコードと見るため、 &&|| で繋げることによって処理が分かれます。

git stash の tips

git stash、使ってますか?
作業中の内容を一旦別に追いやることで他のコマンドを阻害しなくなる便利なコマンドです。
git stashの詳細については、Qiitaやら各種ブログやらで説明していらっしゃる先人がたくさんおりますので、そちらにお譲りします。

tl;dr

git stash <subcommand> stash@{<n>} を省略して git stash <subcommand> <n> と書ける。

本文

みなさんご存じの通り、Gitの各種コマンドには様々なサブコマンドがあり、この git stash も例外ではありません。
この内、

  • git stash show
  • git stash apply
  • git stash pop
  • git stash drop
  • git stash branch

は何も引数を与えず実行すると、暗黙的に stash@{0} が採用されたものと見做されます1

で、引数を与える場合、特にstashの番号を与える場合は stash@{2} とか stash@{3} とか、 stash@{n} のフォーマットで書きます。
これ、数タイプではありますが結構面倒くさいですよね。typoの可能性だって出て来ます。

GitはコミットをSHA1のハッシュで管理しています。
多くのGitコマンドはコミットの指定にハッシュ値の入力を要求しますが2、このハッシュ値は現在のリポジトリの中で一意に特定さえできれば、最低4文字まで省略できることになっています。

例えば、リポジトリcaf1234... というコミットがあり、 他に caf1 で始まる他のコミットがなければ、
git checkout caf1 でこのコミットにチェックアウトすることができます。
caf10... というコミットが他にあれば caf1 では候補が2つあることになってしまうので、
一文字追加して git checkout caf12 でチェックアウトできますね3

で、私思いました。ハッシュ値が省略できるなら、もしかしたらstashの指定も省略できるのではないかと。
何気なく git stash drop 1 と入力して実行してみたら、何と、通りました。

ドキュメントを読んでみるとこんな記述があります。

The latest stash you created is stored in refs/stash; older stashes are found in the reflog of this reference and can be named using the usual reflog syntax (e.g. stash@{0} is the most recently created stash, stash@{1} is the one before it, stash@{2.hours.ago} is also possible). Stashes may also be referenced by specifying just the stash index (e.g. the integer n is equivalent to stash@{n}).

太字部分を雑に訳すと「インデックスだけを指定して参照することもできます(例えば nstash@{n} と等価です)」といった感じでしょうか。

恥ずかしながら、今までこの略記を知りませんでした。
いろいろな git stash に言及している記事を読んでもこの略記について触れているものは私の観測範囲にはありませんでした。
意外と知られていないのかな、と思ってこのエントリを書きました。

ちなみに HEAD@ という略記がありますが、これは結構有名ですよね。

ref: Git - git-stash Documentation


  1. ドキュメントを読むと必ずしも stash@{0} ではないかのようなニュアンスで書かれていますが…

  2. またはシンボル(master とか)やタグなど

  3. 一意に定まらない場合はあいまいな候補のリストを表示してくれます。

Visual Studio Code for Windows の Integrated Terminal に MSYS2 の fish を使う

fish、便利です。

当方は未だにWindows 8.1 UpdateなのでWSL1は使えません。
なので Git for Windows SDK をインストールして簡易的なシェル環境を構築してます。

これが pacman があるのでなかなか快適なんですね。
nodeもこれで入れられるのでchocolateyもほぼ不要になります。

Git for Windows の本来のシェルは Git Bash ですが、他のシェルも pacman 経由でインストールできるため、私は fish をインストールしてこれをデフォルトにしています。

本題です。
慣れたシェルを使っていると、あらゆる場面で導入したくなるもので、VSCode の Integrated Terminal にも fish を導入したくなりました。

で、おもむろに Ctrl + , で設定画面出しますね。

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/fish.exe",
    "terminal.integrated.shellArgs.windows": [
        "-i",
        "--login",
    ],
}

と入力します。2
Ctrl + @ でターミナルを表示するとどうでしょう、エラーの嵐になります。
いろいろ調べた結果、 $PATH がほとんど空っぽになっていることが分かりました。

"C:/git-sdk-64/usr/bin/fish.exe" の代わりに "C:/git-sdk-64/usr/bin/bash.exe" と書いた場合は何の問題もなく起動し、パスも普通に通ってしまうので謎です。

ちなみに bash でうまく行くからと言って

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/bash.exe",
    "terminal.integrated.shellArgs.windows": [
        "-c",
        "/usr/bin/fish",
    ],
}

としても無駄です。
盛大にエラります。

結構な時間を試行錯誤でアレコレし、

{
    "terminal.integrated.shell.windows": "C:/git-sdk-64/usr/bin/fish.exe",
    "terminal.integrated.shellArgs.windows": [
        "-c",
        "eval 'set -x PATH ~/bin /mingw64/bin /usr/bin $PATH; and /usr/bin/fish'",
    ],
}

と書くことでひとまず解決しました。

fish では変数にスペース区切りで値を与えると配列として格納されます。
$PATH も配列で持っているので、その先頭に強引にパスを与えてしまいます。
fish.exe をコールしている段階では $PATH は無事のようなので、ここで set -x PATH ... としてしまいました。3

VSCode には、シェルに与える環境変数をオーバーライドする terminal.integrated.env.windows という設定値もあるのですが、ここだと ~$PATH が展開されない為、已む無くコマンド評価で与えています。

正直言ってワークアラウンド的でスマートさに欠けるので、もっといい方法があれば是非教えていただきたいですね。
あとなぜ $PATH が消え去るのかの原因も分かる方いらっしゃれば…。


  1. Windows Subsystem for Linux

  2. 余談ですが、実は Windows でもほとんどの場面でパスの区切り文字にバックスラッシュ「\」の代わりにスラッシュ「/」を使っても問題ないです。

  3. fish における set -x FOO barbash で言う export FOO=bar のようなもの。