StreamをEvent Handlerを使ってPromiseに変換して成否を待ったり途中で止めたりする

先日 Stream の練習をして分かった気になっていましたが、ダメでした。何がダメだったか。(そもそも普段 Node.js を書いていないのがダメなんだけど。)

  1. 「結局 EventEmitter」って自分で書いといて結果を同期的に「待って」いた1
  2. Stream が Node.js 標準 Stream の Event Emitter を実装しているとは限らない

1 については今回は例によって Promise によって解決します。

2 は具体例を挙げておしまいです。ちゃんと調べろ、に尽きます。

StreamのEvent Listenerでエラーを取得する

Node.js Streams: Everything you need to know – freeCodeCamp.org

ここによくまとまっていて、

Stream#on('error', fn)

で書けそうなことが分かる。

(new Reable())
.pipe(new Writable())
.on('error', () => {
  ..
})

で書ける。ただし、取得して中に処理を書けるだけで返せない。ということは外から処理を与える callback 地獄だ。これは困る。

StreamをPromiseにしてエラー処理のクチを外に出す

イベントと callback が困るなら解決策は Promise になる。たぶん雰囲気はこんな感じだ。

stream2promise(data) {
  return new Promise((resolve, reject) => {
    (new Reable())
    .pipe(new Writable())
    .on('error', (err) => {
      return reject(err)
    })
    .on('finish', () => {
      resolve()
    })
  })
}

うん、なんかイケそう。

ところが実際にはすべての Stream でエラーは起きるし、'error' event そのものは stream 上を伝播したりしないし、'error' event が emit されても処理が止まるわけではないので、これでは不十分。

Stream | Node.js v10.12.0 Documentation

The stream is not closed when the 'error' event is emitted.

具体的には Reable Stream で 'error' が起きた場合でも stream 自体を cancel する人がいなければ Writable Stream の 'finish' event は emit されてしまう。ということは 'error' が起きようが何しようが resolve() が呼ばれる、つまり正常終了してしまうということだ。「'error' event は起きているが正常終了する。」これは期待する動作ではない。

Streamをちゃんと止める

そのためには各 Stream に対してきちんとエラー処理が必要ということになる。

ということでできあがりは以下のようになる。

stream2promise(data) {
  return new Promise((resolve, reject) => {
    const reable   = new Reable()
    const writable = new Writable()

    reable.pipe(writable)

    reable.on('error', (err) {
      reable.unpipe()     // 以降の Stream で余計な event が emit されないように
      return reject(err)  // 起きたエラーを reject に渡す
    })

    writable.on('error', (err) {
      return reject(err)
    })

    writable.on('finish', () => {
      resolve()  // ここに何を渡すかは思案のしどころ
    })

    reable.push(data)
    reable.push(null) // これは標準の Stream の API に従うとこうなる
  })
}

こんな感じ。

うーん、Promise は呼ぶ側はまだ比較的マシだけど、中身はアレな感じになりやすいねぇ。まー何はともあれ、これで以下のように Stream 処理を Promise で受け取れるようになる。

stream2promise(data)
.catch((err) => {
  ..
}).then(() => {
  ..
})

StreamのEventEmitter実装状況には注意が必要

例えば今回書いたコードは Node.js 標準の Stream を基本にしている。前回、「readable#push(null) とか普通書かないだろ」みたいなことを言っていたが、あれは間違いだったことが分かった。実は前回書いたコードでは今回のようなことはできないのだ。

前回は

を利用したが、

  • memory-streams.ReadableStream.on('end') は emit されない
    • ということは WritableStream.on('finish') も emit されない2
  • process.stdout.on('finish') は emit されない

つまり、意図通りに正常終了を判定するのは難しいです。

また memory-streams と似たような機能を持つ

を利用すると stream 処理できない object を String に無理やり変換してしまうので [object Object] が Writable Stream に渡ってしまう。

「メモリの中身を Stream っぽく扱えるコードはすぐできそうじゃね?」と思っていたけど、自分がそんな発想をするくらいなのでやはりカジュアルなコードが多いみたいで、ちゃんと標準の API を使った方がいいなと思い直しましたとさ。

  1. 以前 Promise を使って解決したのと同じなのに 

  2. readが終わらないとwriteも終わらないようだ。実際の処理が終わっていてもこれを検知することができないし、もしかしたらモノによってはwritable streamの動作に支障が出るかもしれない。 

超今さら FeedBurner のお勉強

今や世界一の配信数の座は RSS広告社の各種ブログサービスとの提携とともに奪われてしまった FeedBurner ですが、やはり Feed 配信の実績とそれに関する各種機能と Google のネームバリューでなんとなく世界一的な印象のある FeedBurner をようやく試してみました。

と言っても Feed を配信するだけなら

  1. アカウント取得
  2. 配信したい Feed を登録 -> feedburner 上の URL を決定
  3. 上で設定した URL を feed reader に登録してもらう

だけでおしまいです。

問題は、一時騒がれた

auto-discovery を他ドメインにしてしまうと Yahoo! ブログ検索から外れてしまう

こと。これの対策は

  1. auto-discovery 自体は自ドメイン上の何らかの URI に振っておいて、実際そこにアクセスされたら FeedBurner の bot 以外は FeedBurner ドメインの feed の方に redirect
  2. Yahoo! ブログ検索の方にオリジナルの URI を ping で通知

のいずれかを行えばよいらしい。

今回は今後 feedburner に配信させる feed が増えていく前提で 1 の方法を採用し、mod_rewrite ではなくサーバサイドのスクリプトの方で redirect や URI の管理を行える簡単な仕組みを用意しました。

ちなみに、この確認も rest-client で以下のように行いました。

irb> RestClient.get( URI, {'User-Agent' => 'FeedBurner'} )

要は2番目の引数に request header をいろいろ詰め込めるので、そこに適当に書いてやるだけでオーケー。ブラウザでこれをやると extension を入れるだの普段使わない UI でどこから設定したらいいだか分からないだの面倒だけど、rest-client ならそのまま書くだけなので楽勝です。

LL AHP

Lightweight Language AHP

4大LL(P言語 + Ruby)の中で自分に合ったものをオススメしてくれる。

別にこんなの特定されても構わないので結果を直リン。

Lightweight Language AHP の結果

Ruby (31%) > Perl (26%) > PHP (22%) > Python (21%)

なんかものすごくまっとうな結果が出てしまったような…。Ruby は記述性、と信じちゃいますけど、あとで言葉の定義を読み返すと(こら)変態性もかなり高い部類に入ってると思いますね。というかどんだけ偏った評価してるんだオノレは、という話ですか。

ただこれそもそも使ってなきゃ曖昧なイメージでしか答えられないし、隣の芝生がめっちゃ青く見えてたらそれをオススメされるような気がする。

普段使っている LL は複数選べるようにしてほしかった。Perl も PHP も Ruby も使ってるので。Python は使ってないから知らない、ってだけですな。PHP よりオススメされたらそれなりに面白いとは思ったけど、そういう結果にはならなかった。残念。

Finder から一発で Terminal 上の Emacs を起動する

わたしゃゴリゴリの Terminal 使いですが、何がなんでもキーボード操作じゃなきゃやだ、というわけでもなくて。マウスでポチポチしてたりするときだって当然あるわけです。ファイルをネットワーク越しに転送してきてとりあえず何か開いてみたい、みたいなときとかね。Terminal で cd して less hoge とかすればいいわけですけど、なんとなくドラッグ&ドロップで開きたいときだってあるわけですよ。例え常に Terminal ウィンドウが開いていたとしてもです。開いてる Terminal に emacs と打って目的のファイルをドロップしてフォーカスを Terminal に持ってきてリターン押すだけやん!と言われてもです。

そんなときにね、実は OSX にはコレ!っていうページャやエディタってないわけですよ。どうもどれもこれもイマイチでね。日本語の自動判別がダメだとか設計が古すぎるとか対応しているプログラミング言語が少ないとかそもそもプレインテキストが扱えないだとか、不思議な現象に悩まされることが多いわけです。一応 mi を常備してますけど、なーんかしっくりはこないんですよ。

で、Emacs 使いとしてはじゃあ Carbon Emacs にすれば?みたいな気もしてくるわけですが、Carbon Emacs は先日述べた理由で使いたくない。つか標準の Emacs のほかに Fink の Emacs が入っているのに、そのうえ Carbon Emacs 入れろってか。なんでただでさえ巨大な Emacs を3つも入れないかんのじゃ。

さてどうするか。

閃いたのが「Terminal 上で Emacs を使って目的のファイルを開くって作業をコンテクストメニューとかドラッグ&ドロップでできたらステキ」作戦。

じゃあ、ということで wrapper になりそうなアプリを探すんだが、これが思うような動作をするものってないのね。Terminal にこれこれこう言うコマンドを与えて実行、っていう指定ができて、かつドロップされたファイルを引き数で与えることができればそれでいいのに。1

うーん。AppleScript を使えばできそうだなぁとは思うけど、あれ書くの楽しくないし、もう書いたの1年以上前だし…。

[2006-09-30 追記]でけた。ドロップレットにするには Xcode で Droplet プロジェクトにせんといかんのね。たったこれだけなのに大げさだなぁ。

-- drop-to-emacs

on idle
  (* Add any idle time processing here. *)
end idle

on open names
  set filenames to ""
  repeat with str in names
    copy filenames & " \"" & (POSIX path of str) & "\"" to filenames
  end repeat
  tell application "Terminal" to do script ("emacs" & filenames)
  quit
end open

on idle のときに何か書くべきなのかとかさっぱり分かっていないので放置。

動作としては

  1. 新しい Terminal を起こして
  2. そこで emacs を使って全部のファイルをいっぺんに開く

ただし、Terminal が起動していない場合は、Terminal を起動した際に自動的に作られる新しいウィンドウとは別に自分で Terminal ウィンドウを作成するので、

2個のウィンドウができる

という不細工な動作をする。まぁいいかーと。Terminal そのものはいつも起こしてるから。

しかし、、、AppleScript を書くのは気持ちよくない。いろんな予約語(演算子も予約語みたいなもんだ)があるのに、それいっぺんに確認できるリファレンスないし。つか XCode なんだからリファレンスとか補完とか自分でやってくれりゃいいのになんもしてくんないし。なんか設定足りんのかなぁ。

ま、とにかくできあがったアプリを Dock に置いて完成。いいじゃないの。いーじゃないの。

  1. Terminal に指定コマンドを与える設定を Terminal そのものから「名前を付けて保存」できるんだけど、このアイコンにテキストファイルをドロップすることはできない。 

固定ド、移動ド

Re:ピアノで「ド」の位置がわからない (TomcatがApacheのTLP(top-level project)に /.-j)

なんだこのスレッド。面白い。

Mozilla Japan インタビュー

Mozillaの将来はどこへ? Mozilla Japanに聞く (ITmedia)

順番がおかしいけど。

これを見ると Mozilla Japan としての質を保証しなければ非公式ローカライズビルドの配布は依然可能、ただし、もじら組の一部のプロジェクトは Mozilla Japan に入ったので、もじら組としてのリリースだった場合は今さら非公式リリースに fork するのは面倒というか摩擦を生みかねない、という状況なわけですな。

悩ましいところだ。

「日本発のLinux球団も」ライブドア堀江氏、吼える

from ITmedia

野球の話はどうでもよくて、実はライブドアのシステムの大半は FreeBSD っていうのがちょっと面白かった。自分が FreeBSD に触れたのが7年前(時間経った割にスキル上がらないな)なので時期的にもずいぶん近い。たぶん FreeBSD できているのは当時の時点でのシステム全体の統一感による部分は大きいんだろな。今なら Debian とかのディストリビューションはシステムの統一感もかなりあるので判断の難しいところだろうけど。

FreeBSD はドライバ周りの対応さえ取れれば安定性とシステムの新鮮さを同居させるのが比較的容易な、よいシステムだよな。

あ。情報システム部がマイナスになるって話はずいぶん前からそういう部署が存在しているような、比較的ちゃんとした会社の話ですな。それすらないようなところは安物買いの銭失いをやったり、無駄に重複した買い物したり、セキュリティがズタボロだったり、悪いことの方が多いと思う。

Windows で非対話的インストール

あまり興味のない分野なので情報がない。

いざっちゅーとき英語だとなかなか使えないなぁ。。。

自宅 DNS が立った

念願かなって自宅 DNS サーバが立った。まだ設定で不明瞭なところがそこかしこにあるが、外からも内からも hosts に頼らずに名前解決できるようになった。これで ssh とか irc とか www とかいちいちホスト名の違いを意識せずに済むようになった。嬉しい。

やってることは

  • 自宅サーバの外向きの名前を local で解決
  • それ以外は全部プロバイダの DNS に forward

だけなんだけど、あるとないとで便利さが違う。間違って外向きのドメイン名を打ってルータが反応してしまうとか、邪魔くさいことも起きない。素晴らしい。ただし、dns キャッシュの成果で速くなるかというと、そんなことはない。これは今までもルータの dnsproxy を利用していたから。

About

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