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。
npm scripts で OS ごとに処理を分ける
npm scriptsに clean
ってコマンドを登録しようとしたんですよ。
普通なら 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 integern
is equivalent tostash@{n}
).
太字部分を雑に訳すと「インデックスだけを指定して参照することもできます(例えば n
は stash@{n}
と等価です)」といった感じでしょうか。
恥ずかしながら、今までこの略記を知りませんでした。
いろいろな git stash
に言及している記事を読んでもこの略記について触れているものは私の観測範囲にはありませんでした。
意外と知られていないのかな、と思ってこのエントリを書きました。
ちなみに HEAD
も @
という略記がありますが、これは結構有名ですよね。
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
が消え去るのかの原因も分かる方いらっしゃれば…。