JavaScriptのPromiseでretryするいくつかの方法

JavaScript で Promise はそこそこ扱えるようになったけど、 retry をどうするか曖昧なままだったのでちょっと整理する。

まずダメなやつ

普通に同期的なコードの場合、やり方はいろいろあると思うけど、素朴にはこんな感じに書けると思う。

function s() {
  return Math.random() > 0.5
}

let retry  = 3
let result
while ( retry > 0 ) {
  result = s()

  if ( result ) {
    break
  } else {
    retry--
  }
}
console.log(result)

実際には retry 付きで呼ぶ function に閉じると思うけど、だいたいこんな感じかなーと思う。

で、Promise でもこれを元になんとかしてみようとすると、うまくいかない。s() を Promise を返すようにしてみたとして retry 部分を書き換えるとこんな感じになると思う。

while ( retry > 0 ) {
  result = s()

  s()
 .then(() => {
    break
  }).catch(() => {
    retry--
  })
}
console.log(result)

しかし上のコードを Node.js 6 で実行すると以下のように怒られる。

   break
   ^^^^^

SyntaxError: Illegal break statement

そりゃそうだ

Promise は callback 地獄を解消するものと思われているけど、実際には引数に対してではないけど結果に対して callback を与えて処理をチェインする仕組みであり、ということは上に書いた break の scope は Promise を呼んでいる外側とは異なる、while ループと異なるものになっている。

つまり、break するものがない場所で break しようとしている

Promiseのchainで書く特徴を生かしてシンプルに呼び出す側がretryする例

retry 回数などの抽象化を一切無視すると以下の形になる。

s().catch((err) => {
  return s()
}).catch((err) => {
  return s()
.then((r) => {
  console.log(r)
})
.catch((err) => {
  console.error(err)
})

Promise が rejected になったらもう一度同じ function を call するだけ。これで 3回試行できている。

途中 Promise をわざわざ return しているのは、こうしないと Node.js で UnhandledPromiseRejection の警告が出るからだけど、確かに返しておく方が理に適っていると思う。

cf. 【JavaScript】Promiseのリトライ処理をちゃちゃっと - エムティーアイ エンジニアブログ

呼び出される側がretry込みで実行する例

上のやり方は呼び出す側の処理に retry を入れてしまっているため、呼び出し箇所が複数あった場合に DRY でなくなってしまう。できれば呼び出される側で retry 込みで処理してほしい。

これは再帰させるとすっきり書ける。

function p(retry = 3) {
  return new Promise((resolve, reject) => {
    let r = Math.random()
    if ( r >= 0.5 ) {
      if ( retry > 0 ) {
        p(--retry).then(resolve).catch(reject)
      } else {
        return reject(false)
      }
    } else {
      resolve(r)
    }
  })
}

こうすると p() を呼ぶ側は p() を普通の Promise のように扱っているにも関わらず、中で任意の回数 retry させることができる。

※ retry 回数を引数に与えて再帰で処理していく部分の説明は割愛する。

キモは.then(resolve).catch(reject)

これがないと結果の「クチ」を「戻す」ことができない。つまり retry に入ったら resolve も reject も外からは分からないままなんとなく終了してしまう。

そう、ここで指定している resolve, reject こそが最初の return new Promise() に渡ってくるものであり、呼び出す側の

p()
.then()
.catch()

を実現するために必要なものなのだ。ということで

.then(resolve).catch(reject)

を与える必要がある。

通常の再帰は自分自身を call する際に return しながら戻ってくるのだが、Promise は渡ってくる resolve, reject を then, catch に与えることで「chainableな状態を維持する」形になるようだ。

気持ち悪いが、よく考えると納得はいく。ただこれはイディオムとして覚えてしまうのが早いように思う。

cf.

参考

OpenSSH新機能ヒストリ(6.1〜5.1)

Software Design 10月号の『SSH力をつけよう!』で火がついたのでカッとなってコピペした。

以前

OpenSSH にできること(の一部)ヒストリ - あーありがち(2008-04-08)

ってのを書いてるんだけど、これの続編。例によって全面的に OpenSSH情報 頼みかつ、自分で分かる範囲しかピックアップできてないので漏れまくりかつ間違っている可能性があります!!1

個人的なヒットは

  • 5.4 の netcat mode
  • 5.6 の ControlPersist
  • 5.7 の scp -3
  • 5.9 の ssh_config内でのtty要求, sshd の chroot 対応強化
  • 6.0 の port forward キャンセル

辺りですかね。

netcat modeとControlPersistの個人的まとめ

netcat mode は

ssh -o ProxyCommand='ssh user@dest_host -W %h:%p' gateway_host

こんな感じで使う。これと ControlMaster と scp -3 を組み合わせると割と面倒なネットワークでもそこそこ手軽に作業できそう。

試したところ ControlPersist と netcat mode は両立できないっぽい。netcat mode を使いたい中継 host は限られると思うので、その host でだけ ControlPersist no すればいいのかも。以下のような感じかな。

Host Foo
  ControlPersist no

Host *
  ControlPersist 10

ControlPersist は 全部 pubkey で認証してて ssh agent 使ってれば手間的には要らないっちゃ要らないのでそこまで気にする必要ないかもだけど。(もちろん TCP connection的 には違う)

ProxyCommand も、もちろんいちいち打つのは面倒なので、gateway として使うときの Host と普通にログインするときの Host を分けて設定しておくといいのかも。まとめると以下のような感じか。

Host A-SS
  Hostname foo.example.com
  ProxyCommand ssh user@dest_host -W %h:%p
  ControlPersist no
Host A
  Hostname foo.example.com

Host *
  ControlPersist 10

6.1

  • sandbox有効なsshdをデフォルトに
  • Match で LocalAddress, LocalPort サポート
  • Match で AcceptEnv, Allow Users, Deny Group などをサポート
  • PermitOpen none サポート
  • AuthorizedPrincipalsFile none サポート
  • sshd_config の VersionAddendum でプロトコルバナーに任意の文字列を追加可能

6.0

  • ssh-add -k オプション追加
  • sshd が PermitOpen で * ワイルドカードサポート
  • 多重化された接続での転送のキャンセルをサポート
  • コマンドライン ~C で転送のキャンセルをサポート(多重化されている場合は記法が異なる)

port forward のキャンセルができるのは面白い。

5.9

  • UsePrivilegeSeparation=sandboxモード追加
  • chroot 内の /dev/log が不要に
  • AuthorizedKeysFile は空白区切りの複数パスをサポート
  • ControlPath オプションは %L を接続先ホスト名(の、host portion というのはドメインを除くという意味かな)に展開する
  • ssh_config の Host で否定マッチをサポート
  • ssh_config の RequestTTY で -t/-tt/-T コマンドラインオプションのような tty 要求をサポート
  • ssh-keygen -A オプション追加
  • ssh-add が標準入力をサポート

より chroot-friendly なところと tty 要求が config に書けるのがよさげ。

5.8

bug fix のみ

5.7

  • サポート暗号モードの追加とパフォーマンス強化
  • sftp でのハードリンクサポート
  • sep -3 で remote -> local -> remote コピーをサポート。remote -> remote が直接接続できない場合に有効。
  • sftp クライアントの高速化
  • KexAlgorithms サポート
  • scp での帯域制限コードを sftp にも

scp -3 は遅そうだなというのが最初の感想だけど、ネットワークのポリシーで直通をサポートできないのは方向を含めると意外にありそうなので、便利に使えそうな気もする。

5.6

  • ControlPersist 追加。controlmaster をバックグラウンドに回してくれる機能。
  • sshd および ssh-keygen でのCA鍵のサポート
  • ssh_config 中の Hostname 項目での %h の展開サポート
  • ssh-keygen の import, export 機能でサポートする鍵の追加
  • 認証時の permission チェックの結果を認証成功後に受け取るように
  • 動的な remote forward
  • AuthorizedPrincipalsFile サポート
  • Match ブロック中の AuthorizedKeysFile, AuthorizedPrincipalsFile HostbasedUsesNameFromPacketOnly, PermitTunnel サポート

個人的なポイントは ControlPersist. 詳しくは以下のエントリが参考になります。

OpenSSHのセッションを束ねるControlMasterの使いにくい部分はControlPersistで解決できる - Dマイナー志向

動的な remote forward もたぶんすごいんだろうけど、イマイチよく分かってません。permission チェックのメッセージを成功後にするというのは地味な改善だけどそう言われればそうかという感じ。

5.5

bug fix のみ

5.4

  • ssh protocol 1 をデフォルト無効に
  • (X.509)ではない最小限の証明書フォーマットサポート
  • -W で netcat mode サポート。これまで踏み台で nc を動かしていたようなケースで、手元の ssh が 5.4 以上になれば ssh だけで同様のことが可能に
  • ssh および sshd にて任意の鍵を無効にする機能を追加
  • sftp-server に read-only mode 追加
  • sftp client の各種強化および -P オプションの変更

5.4 の目玉はやはり netcat mode でしょうね。以下が参考になります。

OpenSSH のNetcat modeを使う - TIM Labs

5.3

bug fix のみ

5.2

  • -y オプションで syslog へ
  • sshd_config の ForceCommand が internal-sftp のために引数を取れるようになった
  • -D で SOCKS4A サポート
  • port 0 での remote forward
  • Match ブロックで PermitEmptyPasswords と AllowAgentForwarding サポート

5.1

  • Match address の アドレス/マスク長の指定においてクラシックな * ワイルドカードへのフォールバック付きでサポート
  • sftp が df コマンドサポート
  • MaxSessions サポート。単一TCP接続上のセッション数を 1 や 0 も含めて任意に設定可能。
  • ssh-keyscan が rsa2 をデフォルトに
  • AllowAgentForaward 追加
  • Match ブロック内で MaxAuthTries が設定可能に

Thunderbird のプロファイルを復元

Thunderbird 1.0.x の話。

参考 既存のプロファイルの移動とバックアップしたプロファイルの復元

今回、PowerBook のディスク交換に際して、ちょっと違う方法を採った。profile が一つだけの場合に、profiles.ini を編集せずに復元する方法。

  1. まず、今までの Profiles/ フォルダを丸ごとコピー
  2. 新しい環境で Thunderbird をインストールして起動
    • 新しい Profiles フォルダが作成される
    • アカウントの作成とかインポートとか一切せずに終了
  3. 保存してあった xxxxx.default/ の中身を新しい xxxxx.default/ の中にどかっとコピー
  4. profile の中の chrome/ フォルダにある chrome.rdf を削除
  5. Thunderbird を起動

profiles.ini ファイルの書き換えは行わない。

chrome.rdf にフルパスで以前の情報が保存されていることがあり(確実にそうなのかもしれないけど未検証)、これが新しい環境と食い違う(例えばユーザー名が違う)と Thunderbird は正しく起動できない。xxxxx.default のフォルダ名が食い違っても当然ダメなので、上のリンク先ではこれを profiles.ini ファイルの書き換えで対応しているが、自分は chrome.rdf を消しちゃう方が楽だったのでこの方法にした。

今まで Windows → MacOSX, Windows → Linux → MacOSX などのプラットフォームの移動を経験していて、実はそのたびにアカウントを作成してメールデータとアドレス帳のデータだけコピーしていた。これは profile の中身を丸ごとコピーすると正常に起動できなくなったり UI が乱れるから。しかし上の方法ならたぶんプラットフォームを移行する際にも有効なんじゃないかと思った。ただもうプラットフォームの移行そのものがめったに起きないと思うので、そのときはまた忘れてるような気がするけど。

Website Explorer

いきなりアクセスが増えていたので何事かと思ったらこんなのが来ていた。ただのダウンローダではなく、サイト構造の解析に使えます的なフレコミ。gethtmlw と何が違うのだろうとか思ったけど、フレームを使った HTML で分かりやすく構造を見せてくれるのはちょっと嬉しいかも。使ってみてないから分からないけど、このサイトのアウトラインてのは単なるディレクトリ構造とは何か違うのかな? ディレクトリ構造のことをサイトのアウトラインと言っているならそれは違うだろーと思うけど。

あんまり使用頻度高くないと思うのと、ダウンローダは気をつけて使わないとアタックとみなされたりするぞ(特に最近は回線も太くない、パワーもない自宅サーバが増えてるから)、という注意書きを分かりやすく提示してないのは気になるところ。

ちなみに最近自分がサイトをオフラインで見たいと思ったときは wget の –random-wait つきで落とすようにしている。(場合によっては –limit-rate も。)案外この wait の設定やスピードの設定のできるツールがないのだな。

PukiWiki 1.4.4

attach プラグイン

添付ファイルの permission をプラグイン本体の「中」に手を入れずに、先頭の設定だけで変更できるようになった。プラグインそのものをいじらなきゃいけないのはまだちょっと面倒だけど、まぁだいぶマシになったかな。

backup, diff

diff は実は Windows では間抜けなことに1行開きで表示されていた。改行コード周りの数々の問題を修正した 1.4 だったが、ここは直っていなかった。それだけ diff には需要がないんだろな。これが Unix では問題が見えなくなる。少しは気持ちいい。(バグトラックに入れろってか。)

Windows から Unix に持ってきた backup がなぜか認識されない。wiki, attach, backcup はちゃんと機能しているのに backup だけがおかしい。どういうことやねん。でも調べるのも面倒なので放置決定。

HHK がグッドデザイン賞

GOOD DESIGN AWRD

文字配列ではなく、制御キーに焦点をあて、IT文具としてのキーボードのありかたを提案している姿勢を評価した。

マニアックな評価ポイントだけど、デザインという観点では耐久性や打鍵感覚は評価しようがないのか。

でも無駄なキーの排除はシステムの設計も絡むからなぁ。Windows が十分に使いやすければ Windows キーとか変なショートカットキーみたいなものは用意する必要ないし、専用ワープロ時代のやたらと個別のキーを割り当てる習慣のおかげで AT 互換機の日本語キーボードはスペースバーが妙に小さい。今回の評価ポイントだけじゃその辺には影響はまったく出そうにないのがちとくやしいというかなんというか。まぁ HHK は使ってないんだけどさ。

ともかく、キーボードのデザインて実は大事なのよ、ということを少しは説明しやすくなるかもしんないな。

About

例によって個人のなんちゃらです