Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 1/248: 1 2 3 4 5 6 7 8 9 »

JS開発のつらみ - Nov 20, 2023

JavaScript界隈はソフトウェアのトレンドの移り変わり・流行り廃りが激しい、とはよく聞く。 「だから辛い」とはどういうことなのか、について考えたことのあれこれをXに垂れ流したのを、再編集してまとめた。

続きを表示する ...

「何もしてないのに急にnpm installできなくなった」への立ち向かい方 - May 06, 2021

(この記事はQiitaとのクロスポストです。)

初心者丸出し感がものすごいのですが、標題の通りのことが起こってしまいました。とあるJavaScript製の自作ソフトウェアの文法チェックにeslintやjsonlint-cliを使いたくて、package.jsonを置いておいて、npm installでそれらをインストールできるようにしていたのですが、それが突然以下のようなエラーで止まるようになってしまいました。

$ npm install
npm ERR! invalid options argument

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-05-xxTxx_xx_xx_xxxZ-debug.log

他の自作ソフトウェアでも、npm installしようとすると同じエラーで止まってしまいました。メッセージには、詳細を見たければログを読むように書かれており、そのログファイル(/root/.npm/_logs/2021-05-xxTxx_xx_xx_xxxZ-debug.log)を見ると、具体的なエラー箇所は以下のような感じでした。

27 verbose stack TypeError: invalid options argument
27 verbose stack     at optsArg (/usr/local/lib/node_modules/npm/node_modules/mkdirp/lib/opts-arg.js:13:11)
27 verbose stack     at mkdirp (/usr/local/lib/node_modules/npm/node_modules/mkdirp/index.js:11:10)
27 verbose stack     at tryCatcher (/usr/local/lib/node_modules/npm/node_modules/bluebird/js/release/util.js:16:23)
27 verbose stack     at ret (eval at makeNodePromisifiedEval (/usr/local/lib/node_modules/npm/node_modules/bluebird/js/release/promisify.js:184:12), <anonymous>:13:39)
27 verbose stack     at Object.mkdirfix (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/cacache/lib/util/fix-owner.js:36:10)
27 verbose stack     at makeTmp (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/cacache/lib/content/write.js:121:19)
27 verbose stack     at write (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/cacache/lib/content/write.js:35:19)
27 verbose stack     at putData (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/cacache/put.js:11:10)
27 verbose stack     at Object.x.put (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/cacache/locales/en.js:28:37)
27 verbose stack     at WriteStream._flush (/usr/local/lib/node_modules/npm/node_modules/npm-registry-fetch/node_modules/make-fetch-happen/cache.js:156:21)
27 verbose stack     at WriteStream._write (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/index.js:36:35)
27 verbose stack     at doWrite (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/node_modules/readable-stream/lib/_stream_writable.js:428:64)
27 verbose stack     at writeOrBuffer (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/node_modules/readable-stream/lib/_stream_writable.js:417:5)
27 verbose stack     at WriteStream.Writable.write (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/node_modules/readable-stream/lib/_stream_writable.js:334:11)
27 verbose stack     at WriteStream.end (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/index.js:45:41)
27 verbose stack     at WriteStream.end (/usr/local/lib/node_modules/npm/node_modules/flush-write-stream/index.js:42:47)

依存関係のどこかで、あるライブラリの動作が変更され、それに依存していた別のライブラリが動作しなくなる、という事態は度々発生します。RubyでもNode.js(JavaScript)でも、おそらくはPHPでもPythonでも、パッケージマネージャを使っていると、「色々なライブラリが依存関係で自動的に入ってくるけれども、そのそれぞれの内容はチンプンカンプン。にもかかわらず、依存関係が原因の実行時エラーが発生してしまって問題解決の糸口すら掴めない」という状況が発生しがちでしょう。

僕はJavaScriptを長く書いてきてはいますが、パッケージマネージャそのものには明るくなく、このような状況が発生するとお手上げになりがちで、そういう意味ではまったく初心者レベルと言えます。今回も、エラーが出た瞬間に顔が真っ青になり、操作を繰り返しても状況が変わらないことでさらに血の気を失う、という絶望的な状況でした。

結論から先に言うと、この問題はNode.jsを入れるのに使っていたnのバージョンが古かったせいで発生していました。n自体を最新の物に入れ換えてn 16.1.0で現行の最新リリースのNode.jsを入れ直した所、この問題は無事に解消され、npm installが成功するようになりました。

以下は、今回この問題の解決策に辿り着き、原因を把握するまでの過程で行ったことの記録です。今回の問題自体は、誰の環境でも起こるという物ではないので、直接の参考にはならないと思いますが、未知の物と立ち向かいながら調査をして、現状を把握し問題解決を図る際の、暗闇の歩き方の参考にしてもらえれば幸いです。

続きを表示する ...

ES moduleとして簡単に静的に検証できるテストを書けるようになって(個人的に)幸せが増した話 - Jun 03, 2020

この記事はQiitaとのクロスポストです

テスト実行後に気付くのってあほくさいじゃないですか?

ES moduleのコードでは「関数名や変数名の誤記」をESLintなどで容易に静的に検出できます。なので、JSで書ける物は片っ端からなんでもES moduleにしたくなるのですが、いわゆる自動テストのコードでは、この性質がかえって邪魔になることがあります。

たとえばMochaのテストケースには、何の前触れもなくdescribe()it()などの関数が登場します。これらが「未定義の関数呼び出し」としてエラーにならないようにするには、テストと実装でESLintのルールを切り替えて警告条件を緩和したり、テスティングフレームワークの関数やオブジェクトを警告の例外に明示したりといった対策が必要になります。

しかし、警告を甘くすればテストだけ静的な検証が甘くなりますし、警告の例外を指定するのもテストの作成やメンテナンスが煩雑になります。テストの書きやすさ・維持しやすさと静的な検証の完全性とを両立しにくいのは、JSで物を作る時に個人的にずっと気になっていた点でした。

Pure ES modulesなテスティングフレームワークつくった

というわけで、このような難点がないPure ES modulesなテスティングフレームワークを作ってみました。

Node.js v13以上の環境で、npm install tiny-esm-test-runnerでインストールできます。

元々は、特定プロジェクトでのCI用に簡易的なテストランナーを書いて使い捨てにしていたのですが、それを複数プロジェクトで使い回すうちに、さすがメンテナンスが面倒だと感じるようになってきたため、この度一念発起してきちんと整備したという次第です。

基本的な使い方

テストはES moduleのファイルとして記述します。以下に例を示します。

続きを表示する ...

Virtual DOMでなく生のDocumentFragmentを与えてDOMを差分更新したいって話 - Mar 09, 2020

この記事はQiitaとのクロスポストです。

概要

FirefoxのアドオンやChromeの拡張機能向けに、名前空間をまたいでDOMに変更を差分適用したい場面で使える、Virtual DOMでないReal DOMで差分適用する、webextensions-lib-dom-updaterという名前のライブラリをつくりました。

どう使うの?

クライアント側でタブの情報を取得して、サーバー側でそれをレンダリングする、という場面であれば以下のようになります。

クライアント側(制御担当):

// IDからタブのオブジェクトを得る(WebExtensionsのAPI)
const tab = await browser.tabs.get(tabId);
// プロセスをまたいで、レンダリングして欲しい内容を送る
browser.runtime.sendMessage(
  '受信側の識別子',
  // ↓テンプレート記法でHTMLのコード片をそのまま生成
  `
    <span id="tab"
          class="${tab.active ? 'active' : ''}">
      <span id="throbber"
            class="${tab.status}">
        <span id="throbber-image"
              class="${tab.status}"></span>
      </span>
      <img id="favicon"
           class="${tab.status}"
           src="${tab.favIconUrl}">
      <span id="label">${tab.title}</span>
    </span>
  `.trim()
);

サーバー側(画面描画担当):

import { DOMUpdater } = './dom-updater.js';

// 他のプロセスからのメッセージを待ち受ける(WebExtensionsのAPI)
browser.runtime.onMessageExternal(message => {
  // 反映先の要素
  const before = document.getElementById('container');

  // 反映する内容をDocumentFragmentにする
  const range = document.createRange();
  range.setStart(document.body, 0);
  const after = range.createContextualFragment(message);
  range.detach();

  // DocumentFragmentの内容でbeforeと異なる部分があれば、
  // それをbeforeに差分適用する
  DOMUpdater.update(before, after); // ←これを作った。
});

どの辺に新規性があるの?

Virtual DOMでなく生のReal DOMを更新内容として指定する(※例ではDocumentFragmentを使ってますが、普通のElementでも構いません。)ので、Virtual DOMの独自記法を覚えなくていいです。利点はそれだけです。

既に同じ事をするライブラリが世の中にはあったのかもしれませんが、自分には見つけられませんでした。どなたかご存じでしたら教えてください……

→と書いていたら、morphdomという似た趣旨のライブラリが既にあると教えて頂きました。今回実装したものとの比較を最後に追記しました。

なんで作ったの?

Tree Style TabというFirefox用アドオンで、「他のアドオンから指示して、タブの中に任意のUI要素を追加する」という事をやるために作りました。

(スクリーンショット:Tree Style Tabでの利用例) 見た目を元々のタブに合わせているのでちょっと分かりにくいですが、このスクリーンショットの左側で「Add-ons - Mozilla | MDN」というラベルを伴って表示されている「細いタブっぽい物」が、別のアドオンから指示された通りの内容を、このライブラリによる差分適用で埋め込んだ部分です。

アドオン間での通信ではJSONオブジェクト形式のメッセージしか扱えないため、こういう事をやろうとすると

  • 挿入するUI要素の内容を、どんな書式でどうやって指定させるか
  • 挿入されたUI要素を、どんな書式でどうやって指定して更新させるか

ということを決める必要があります。

Virtual DOM使えばいいやん

DOMの変更の差分適用といえば既存のVirtual DOM専用のライブラリは既にいくつもあって、

このあたりの記法をそのまま使えばいいといえばいい話です。

が、どれもべつに「スタンダード」というわけではないようなので、どれを選んでも後で文句を言われそうな気がします。宗教戦争がもしあるなら、そこに参戦したくはないですし、ただでさえ「Tree Style Tabが他のアドオン向けに提供する独自のAPI」というめちゃめちゃニッチな場面なので、こんな限定された場面のために新たに(もし普段から使っている物があるなら、それとは別のライブラリ由来の)独自の記法を覚えてもらうのは忍びないです。というか、自分がこれ以上覚えたくありません。

その点、HTMLのソースを文字列で指定してDOMの標準的な機能でNodeやDocumentFragmentにするという事にしておけば、多少冗長ではあるものの、「デジュールスタンダードなんで」と言ってしまえます。技術選択で悩まなくてもよくするためだけの選択というわけです。

続きを表示する ...

絵文字✨とTwitter Bot🤖と私👤とEmoji Editor📝 - Dec 15, 2016

このエントリは絵文字Advent Calendar 2016とのクロスポストです。(→Qiitaの方の投稿

この記事では、自分が絵文字込みのテキストを楽に編集するために作ったEmoji Editorという簡単なツールを紹介します。

PCでパレットから絵文字を入力したいんです……😭

唐突ですが皆さん、どうやって絵文字を入力されてますか?

Android版のGoogle日本語のように絵文字のパレットが付いていたり、macOSの日本語入力のように標準辞書に入っていて普通に「すし」と入れて変換すれば「🍣」になったりする環境もあるようなのですが、自分が主に使っているWindows 7+ATOK2016の環境とUbuntu 16.04LTS+ATOK X3の環境では良い方法がなさげです。自分は絵文字はパレットから選択したい派、というか顔文字に対応する読みをいちいち覚えてられない派なので、やるなら一覧の中からぽちぽちクリックして選びたいです。しかしATOKに付属の文字パレットというユーティリティの分類には「顔」みたいなグループ分けが無いため、Unicode絵文字を使おうと思うとUnicodeの表のあっちこっちを行き来しながら探さないといけません。(最新のATOK2017ではこの辺どうなんでしょう?)

また、これはカラー絵文字のフォントが無いという古い環境だからのようですが、文字パレット上だけでなくテキストエディタ上でも絵文字は白黒表示です。自分が絵文字を使いたいのは主にTwitterでの投稿なので、見るならTwitter上でどう見えるかが分からないと安心して使えません。

元々絵文字を使う習慣が無かった自分が絵文字に触れる機会が増えたのは、「シス管系女子」の広報用Twitterアカウント(みんとちゃんbot)がきっかけです。いつもの自分のノリでやると堅すぎると思ったので、みんとちゃんbotの発言についてはあえて絵文字を入れて柔らかい印象を持ってもらえるようにしたい、という下心ドリブンです。

当初はAndroidのGoogle日本語入力から絵文字を入れていましたが、運用のためのbotスクリプトを作成して、発言データにするためのテキストを大量に用意する段階になってPC上で作業に入ろうとしたときに、前述のような状況に気が付きました。

そこでとりあえずTwitter絵文字が使えるパレットを探してみた所、テスト用にちょっと投稿するだけならTwitterの絵文字をデスクトップPCから使おう! Ver2というサービスで絵文字の入力自体はできる事が分かりました。が、このページは既に作成済みのデータの編集には使えません。やはりここは、Twitter絵文字を含んだプレーンテキストをWYSIWYGで編集できるツールが欲しくなるところです。

探し方が悪かったのかもしれませんが、自分はその時「まさにこれだ!」と思える物を見つけられなかったため、無いなら作るか……という事で作りました。その名もEmoji Editor(名前が安直すぎる)。HTMLファイル1つだけで完結する、フレームワークも何も使ってないSPAとも呼べないような代物です。 (Emoji Editorの画面)

このエントリの公開を機に、GitHubにリポジトリを作りましたので、forkしたりプルリクしたりして頂ければ幸いです。

続きを表示する ...

system-admin-girl.comのこと - Dec 04, 2015

シス管系女子の特設サイトができました、というか例によって自分で作りました。 3日ほど夜なべして。

どうしてこうなった

電子書籍はいわゆる印税契約だけど紙の方は原稿料買い切り(書籍じゃなくムックだから)なので、プロモーションに工数かけてもあまり得にならないんですよね。なのに何故やったのかというと……要するに、欲しかったんですよ!!! 僕が!!!!!

いやね、連載5年目に入ろうとしてるのにWeb上では相変わらず知名度が低くて、知ってる人は知ってる的な立ち位置がいいかげん辛くなってきたというか、この間なんて「たまたま日経Linuxを見たらこんなの(#!シス管系女子Season3 Petitまとめ読み)あったんだけど、これってもしかしてシェルスクリプトマガジンのシェル女子の便乗企画……?」みたいに思われてしまった、というのは被害妄想もいいとこなんですが、「それもこれもみんな、ここ見れば大体分かるっていう位置付けの公式サイトが無いせいなんや!」と大人げなく嫉妬に狂いまして、手元にあった素材と原稿データをイラレの上で切り貼りしていわゆる1枚ペラのページのこんな妄想画像 を作って「こういうのがほしいんだよこういうのがああああああ!! 日経Linuxのサイトの中に特設ページ作ってもらえませんかね!?」と日経BPサイドに提案してみたものの、会社の方針とかであんまり他のページと違う物は載せられないのでPDF置いとくだけならまぁなんとか……と言われてしまって「そういうことじゃないんだよおおおおお!!!」と血の涙流しながらHTMLとCSSをゴリゴリ書いてさくらのレンタルサーバの一番安いプラン借りてお名前.comでドメイン取って(独自ドメイン取るのこれが初めてですよ! なんと!)突貫工事で作った、というのが真相ですハイ。 説明文が「Piro氏」とか微妙に客観なのは、元が日経BPへの提案用だったからで。

3日でできたのは十数年越しのイメトレのおかげや……

思い返せばかれこれ14~5年は前ですかね? CSSコミューンで偉そうなこと言ってて「Web業界に進みたいな」とか一瞬思ってたあの頃。 CSS2の仕様通りに書いた物をInternet Explorerが微妙にまともにレンダリングしてくれなくて、それでもNetscape Communicator 4での悲惨な対応具合に比べればまだマシというネスケ派の自分にとっては「ギギギ……!」と歯ぎしりせずにはおれない状況で、NC4でもIEでもきちんと表示できてW3C的にも(というかAnother HTML-lint的に?)Validで且つそこそこ凝った見た目を実現して「W3Cの理想は非現実的な絵空事なんかとちゃうんやで!!!」と世界の片隅でアピールしたい!という思いからこんなスタイルシートあんなスタイルシートそんなスタイルシートといったいろんな実験作を書いて粋がってた日々。 当時最もCSS2の実装が進んでたGeckoエンジンでさえできることは全然限られていて、「あぁ……この仕様にある:nth-child(2n-1)ってのが使えれば装飾の左右振り分けも簡単なのに……!」と思いながらclass="even"とかclass="odd"とか書いて騙し騙しやってましたとも。 ドロップシャドウひとつ作るのにも画像を作ってスライスして……よくあんなめんどくさいことやってたもんだ。 しかも人力で。 (素人でお金も無いのでDream WeaverだのFireworksだのは手が出なかった)

素人の僕でこんなんだった訳だけど、当時から業務として手がけておられた方々は僕なんかよりはるかに切実な思いでこういう事と向き合っていたのであろう。 CSS昔話 Advent Calendar 2015 - Adventarにはそういう時期の苦労話がたくさん集まってきそうな気配を感じている。

それが、今じゃどうですか。 <ruby><rb>文字の影</rb><rp>(</rp><rt>text-shadow</rt><rp>)</rp></ruby>も<ruby><rb>ボックスの影</rb><rp>(</rp><rt>box-shadow</rt><rp>)</rp></ruby>も<ruby><rb>角丸</rb><rp>(</rp><rt>border-radius</rt><rp>)</rp></ruby>も背景色の半透明もグラデーションも<ruby><rb>奇数番目と偶数番目での振り分け</rb><rp>(</rp><rt>:nth-child(2n-1)</rt><rp>)</rp></ruby>も、テキストでちょちょいっと書けば即反映ですよ。 当時は想像もしてなかった、CSSメディアクエリーなんて物もできてるし。 あの頃「こういう風に作れればいいのになあああ!!!」とイメージトレーニングしていた理想のCSS世界がまさに目の前にあるという感慨深さよ。 document.querySelectorAll()で要素をガッと集めてきて制御したり、今画面内にある画像をdocument.elementFromPoint()でダイレクトに取得して位置合わせしたり、Firefoxのアドオン開発でも苦労してた部分があっさりクロスブラウザで動いてくれちゃってて。 FirefoxとChromeのWindows版とAndroid版でだけ検証してリリースしちゃいましたが、後でIE11で見たら全く支障なく完璧に表示されてたし。 あまりのあっけなさに目がテンになり、その後感涙でむせび泣きかねない勢いでした。

SNSでシェアしやすく

……とまあ、昔取った杵柄で一枚ペラ&試し読み簡易マンガビューワーを作るところまではよかったんですが、宣伝のためのサイトなのにSNS連携のシェア用ボタンを入れてなかったり、FacebookやTwitterでシェアされたときにいい感じに画像を出す工夫をしてなかったり、「それやらないの今時あり得ないでしょ……」な手落ちだらけで、「エッなにそれいつの間にそんな事になってたの」と完全に浦島太郎でした。 かろうじてGoogleアナリティクスは存在を知っていたので、それだけは入れてましたが。

皆さんの助けが無ければ、作ったはいいものの結局やっぱり誰にも見られない廃墟サイト化一直線……という末路を辿っていたところでした。 大変お世話になりました。

良い物作ってるつもりでもプロモーションできてなきゃ存在しないのと同じ

思い出話8割に実用情報2割くらいでこんなエントリを書き記して何やってんのって自分でも思いますが、今僕が主たる仕事の場にしてるフリーソフトウェアの世界でも、プロモーションはやっぱ大事だなって思うんですよね。 先行実装があってそれなりに頑張って丁寧に作ってたのに、その存在を知らなかった人が後から作った荒い出来の物が「これ新しい!!! こんなの欲しかった!!!!」って人気をかっさらってって、先達の頑張りが全く誰からも評価されないまま消えていってしまう……俺達は、あと何回そんな悲劇を目にすればいい? 俺はあと何回、顧みられることの無い先駆者を目にすればいいんだ?

というわけで、今後仕事絡みで何か作って公開する時にまた参照したいので自分用のメモとして今回参照した情報をまとめたというのがこのエントリの趣旨なのでした。

まぁ、このsystem-admin-girl.comが実際どれだけ成果が出るかというのは分からない、ともすれば結局やっぱり廃墟になっちゃったねというオチもあり得そうではありますが、「だからこういう風にして欲しかったんだよぉぉおおおお!!」と地団駄踏んで不満溜め込んでるよりは、「思ってたやりたかったとおりの事やったけど駄目でしたわハハハ……」となる方がまだ精神衛生上良さそうなので、これで安心して眠れます。 おやすみなさい。

ページ内の見出し一覧をMarkdownのリスト形式で出力するbookmarklet - Jul 05, 2012

前に似たような事をやった気がするけど。

javascript:

var tab  = '   ',
    min = prompt('Input minimum level of the headings (default=1)');

function tabs(n) {
  var ret = [];
  for (var i = 0; i < n; i++) ret.push(tab);
  return ret.join('');
};

function collectHeadings(minLevel) {
  var rawHeadings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
  var headings = [];
  var heading, node;
  for (var i = 0, maxi = rawHeadings.length; i < maxi; i++)
  {
    node = rawHeadings[i];
    heading = {
      node: node,
      id: node.id,
      label: node.textContent,
      level: parseInt(node.localName.charAt(1))
    };
    if (heading.level >= min)
      headings.push(heading);
  }
  return headings;
}

function generateList(headings) {
  var list = [],
      h,
      id,
      item,
      nest = 0;
  for (var i = 0; i < headings.length; i++)
  {
    h = headings[i];
    id = h.id || h.node.parentNode.id || h.node.parentNode.parentNode.id;
    item = (id) ? '['+h.label+'](#'+id+')' : h.label ;
    if (i > 0) {
      if (h.level > headings[i-1].level) {
        nest += 1;
      } else if (h.level < headings[i-1].level) {
        nest -= 1;
      }
    }
    if (nest == 0) {
      list.push(' * '+item+'\n');
    } else {
      list.push(tabs(nest)+'* ' + item + '\n');
    }
  }
  return list.join('');
}

var headings = collectHeadings(Math.max(1, min));
var list = generateList(headings);
if (list) window.open('data:text/plain,'+encodeURIComponent(list));

見出しレベルの関係がちゃんとしてないとうまく動かない。あとsection/headingのネストには対応してない。

JSDeferred - Why this code doesn't work? - Jun 12, 2012

I got a mail.

I've started to use jsdeferred 2 days ago and I'm really really happy with it because I consider it's really easy to use, light and it looks very powerfull. Anyway, I have one little doubt I hope you can help me about.

Here's the thing, I would like to deferredize some functions to use in some chains, but I haven't figured out what's the best way to get it, I put you a little example

If I try something basic like this:

function my_deferred(data) {
  var deferred = new Deferred();
  deferred.call(data);
  return deferred;
}

function main() {
  Deferred.define();

  my_deferred("foo").
    next(function(data) {
      console.log(data);
    });
}

The chain is not executed (I don't know why). But if I change to something like this, using setTimeout, it works:

function my_deferred(data) {
  var deferred = new Deferred();
  setTimeout(function() {
    deferred.call(data);
  },1);
  return deferred;
}

function main() {
  Deferred.define();

  my_deferred("foo").
    next(function(data) {
      console.log(data);
    });
}

The question is, wouldn't it be possible to get the right behaviour of the chain without using setTimeout? I would like to deferredize some functions without the penalty of using setTimeout.

You should use Deferred.next() ( it is available as a global function next() if you use Deferred.define() ) instead of setTimeout(), like:

function  my_deferred(data) {
  var deferred = new Deferred();
  Deferred.next(function() {
    deferred.call(data);
  });
  return deferred;
}

Deferred.next() works just like setTimeout(), but in most cases it works faster than setTimeout().

deferred.call() just calls the next job which is registered as a callback function given to its .next() method. So, you have to call deferred.call() after you register the next job.

However, In your case, any "next job" has not been registered yet when you call deferred.call(). As the result, the "next job" registered after deferred.call() was called won't be called by anyone.

function my_deferred(data) {
  // (3) Now, we are in this function.
  var deferred=new Deferred();
  deferred.call(data); // (4) deferred.call() is called, then
                       //     the next job ( registered via
                       //     deferred.next() ) is also called.
                       //     However, it has not been registered yet.
                       //     As the result, nothing happens.
  return deferred; // (5) We return the deferred object and
                   //     exit from this function.
}

function main() {
  // (1) Starting position of this event loop.
  Deferred.define();

  // (2) The function is called.
  my_deferred("foo").
    next(function(data) {
      // We never come here!!
      console.log(data);
    }); // (6) You register the next job to the returned deferred
        //     object, via its next() method. However, because
        //     deferred.call() was already called, the job will
        //     never be called by anyone.
  // (7) End of this event loop.
}

Instead,

function my_deferred(data) {
  // (3) Now, we are in this function.
  var deferred=new Deferred();
  Deferred.next() {
    // (8) Now, the next event loop is started.
    deferred.call(data); // (9) deferred.call() is called, then
                         //     the registered next job is also called.
  }); // (4) We just reserve the task for the next event loop.
  return deferred; // (5) We return the deferred object and
                   //     exit from this function.
}

function main() {
  // (1) Starting position of this event loop.
  Deferred.define();

  // (2) The function is called.
  my_deferred("foo").
    next(function(data) {
      // (10) We are here!
      console.log(data);
    }); // (6) You register the next job to the returned object.
  // (7) End of this event loop.
}

As above, you must register the next job to the deferred object before its call() is called.


ちょよんごさんによると、このメールの送り主の人(ここでは省いたけど、WebGL用のスクリプトを書いてるそうです)は、JSDeferredを使ってるとおぼしき開発者に手当たり次第質問を投げてるらしい。そんなこととはつゆ知らず「僕作者じゃないんだけどなあ」と呑気にちょよんごさんに転送してしまって、余計なストレスをかけてしまったようなので罪滅ぼしと思って真面目に返信してみた。伝わってるかどうかは分かんないけど。

これはJSDeferredの結構典型的なハマリ所だと思う。XMLHttpRequestとかNode.jsの非同期なメソッドみたいに、コールバック関数が必ず次以降のイベントループで呼ばれる物に対してであれば、このメールの送り主の人が書いてるような

var Deferred = require('jsdeferred').Deferred;
var fs = require('fs');

function statDeferred(filePath) {
  var deferred = new Deferred();
  fs.stat(filePath, function(error, stats) {
    if (error)
      deferred.fail(error);
    else
      deferred.call(stats);
  });
  return deferred;
}

statDeferred('/tmp/foobar')
  .next(function(stats) {
    if (stats.isDirectory()) {
      ...
    }
  });

こういう流れの書き方で何も問題ない。

問題は、やらせたい処理が同期的であった場合で、

function statDeferred(filePath) {
  var deferred = new Deferred();
  try {
    var stats = fs.statSync(filePath);
    deferred.call(stats);
  } catch(error) {
    deferred.fail(error);
  }
  return deferred;
}

これでは動かないのですよね。 何故かというと、 deferred.call() は「その deferrednext() に渡された関数、という形で あらかじめ 登録されていた『次のジョブ』を実行する」物だから。 この例だと statDeferred()deferredreturn する前に deferred.call() を呼んでしまっているから、その後で次のジョブを登録しても、登録した「次のジョブ」を呼ぶ人が誰もいないわけです。

function statDeferred(filePath) {
  // (2) 関数の中に入った。
  var deferred = new Deferred();
  try {
    var stats = fs.statSync(filePath); // (3) 処理を実行した。
    deferred.call(stats); // (4) 登録済みの「次のジョブ」を実行しよう……
                          //     とするんだけど、まだ何も登録されてないので、
                          //     当然何も起こらない。
  } catch(error) {
    deferred.fail(error);
  }
  return deferred; // (5) 関数を抜けた。
}

// (1) 今のイベントループでのスタート地点。
statDeferred('/tmp/foobar')
  .next(function(stats) {
    // 「次のジョブ」の登録後に deferred.call() が呼ばれないので、ここには到達しない。
    if (stats.isDirectory()) {
      ...
    }
  }); // (6) 戻り値の next() を呼んで、「次のジョブ」を登録した(今更)。
// (7) 今のイベントループの終わり。

だから、 setTimeout() なり Deferred.next() なりを使って、 deferred.call() を呼ぶ処理を「次のジョブを登録する処理」よりも後(=次のイベントループ)に実行する必要がある。そうすれば、

function statDeferred(filePath) {
  // (2) 関数の中に入った。
  var deferred = new Deferred();
  Deferred.next(function() {
    // (7) 次のイベントループで、予約されたこの処理が始まる。
    try {
      var stats = fs.statSync(filePath);
      deferred.call(stats); // (8) 登録済みの「次のジョブ」を連鎖的に呼ぶ。
    } catch(error) {
      deferred.fail(error);
    }
  }); // (3) 次のイベントループで実行するように予約。
  return deferred; // (4) 関数を抜けた。
}

// (1) 今のイベントループでのスタート地点。
statDeferred('/tmp/foobar')
  .next(function(stats) {
    // (9) 「次のジョブ」として、この処理が始まる。
    if (stats.isDirectory()) {
      ...
    }
  }); // (5) 戻り値の next() を呼んで、次のジョブを登録した。
// (6) 今のイベントループの終わり。

……という順番で処理が進むから、期待通りに動くようになる。

どの処理が一体何をしているのか、どの関数が何を返しているのか、自分が呼んでいるメソッドは誰のメソッドなのか、といった事をちゃんと理解しておくと、この話がすっと頭に入ってくると思うんだけど、ただの構文として覚えてしまっていると、メールの送り主の人みたいに「何で動かないの……」って詰まってしまうんだと思う。

という風に偉そうな事を言ってるけど、僕自身も最初は多分ただの構文として覚えてた方で、ただ、「同期処理の時は setTimeout() なり Deferred.next() なりを使って deferred.call() を呼ぶ」という所までを「構文」として暗記していたから、結果的にはちゃんと動いてくれてたという状態だったんだと思う。その後若手IT勉強会の中でコードリーディングをやってだんだん仕組みが分かってきて、「ああなるほど、こういうことだったんだ」と理解できるようになった。

Firefox Hacks Rebootedでもいっぱい書いたけど、JSDeferredの本質は、「あらゆる処理について、その次にやらせたい仕事を next() のコールバック関数という形で後から登録できるようにする」ものだ、と僕は認識してる。「後から登録する」という都合上、この性質は非同期処理(関数を抜けた時点でまだ処理が始まっていないという種類の処理)と非常に相性がいい。けれども、非同期処理じゃないと使えないわけではない。ただ、「後から登録する」というのは、普通に順番通りに進む同期的な処理の中では行えないイレギュラーな事だから、 setTimeout()Deferred.next() を使わざるを得なくて、それで初見の人に分かりにくくなってしまう。そこがもどかしい所だ。

FirefoxのmozRequestAnimationFrame()の仕様が変わってた - Dec 02, 2011

2011年11月30日付けのNightly 11.0a1でツリー型タブが動かないっていう報告を受けたので調べてみたら、なんかJavaScriptベースでアニメーション効果を実装する時に使えるFirefox 4から(だったっけ?)の新しい機能に基づいたアニメーション管理のための仕組みが期待した通りに動かなくなってて、あれこれ試してるうちにどうもmozRequestAnimationFrame()の使い方のうちDOMイベントベースでやる方のが動かなくなっててコールバック関数を使う方法でならうごくっぽいという事が分かったので、ツリー型タブ0.12.2011120101からはそのように変更した。

えむけいさんが教えて下さった所によると、これはBug 704171 – Remove the no-argument form of requestAnimationFrameでの変更による物で、要するに「DOMイベントベースの方法は正式な仕様になりそうにないし誰も使ってないし、もう廃止してよくね?」ってことで、Geckoでしか使えなかったDOMイベントベースの方法は廃止されて、WebKit等でも利用できるのと同じ形式のAPIに統一されたんだそうだ。まさにその当日にMDNのドキュメントを調べてて、その廃止された方の使い方が解説されていて、MDNにも書いてある使い方なのにおっかしーなーと首をひねってたんだけど、単にドキュメントの更新が間に合っていなかっただけだった。

現実的には妥当な決定だと思うけど、なんか釈然としない。例外のメッセージで「仕様が変わった、古い使い方はもう使えない」とかそういう情報を出してくれれば、もうちょっとすんなり原因に気がつけただろうに……

Firefox Hacks Rebooted、オライリーから出ました - Oct 27, 2011

既にご存じの方もいらっしゃるかとは思いますが、オライリーから「Firefox Hacks Rebooted」という本が出ました3年前に出た「Firefox 3 Hacks」の続き……でもないのですが、似たようなコンセプトで「今」の話をまとめた本です。

本1冊を通して1つのストーリーに基づいて何かを体系立てて解説する、という本ではなくて、各著者が自分の得意分野で好きなように書きたい事を書きまくった本、というのが実情を正しく言い表している気がします(元々、オライリーの「○○ Hacks」というタイトルは「なんでもあり」のブランドなんだそうで……その言葉に甘えてしまいました)。

以下、各章の対象読者層と思われる層、内容の簡単な紹介と、定量的に傾向を把握するための手がかりとしてページ数と全体に対する割合を表にしてみました。これを見ると一発で分かりますが、僕(4章と6章の一部を担当)の暴走が著しいですね。やばい。

主な対象読者層 内容 ページ数 全体の中でのパーセンテージ
1章 エンドユーザ Firefoxの新機能紹介など基本的な事。Firefox 3.6とFirefox 4以降との間で何が変わったのかのまとめ。 54 11%
2章 開発者寄りのユーザ VimperatorとKeySnailの解説。と、Twitter用アドオンの解説。ブラウザとしてのFirefoxをガンガン使いこなしていく人向け。 42 9%
3章 アドオン開発者 Add-on SDKを使ったアドオン開発のチュートリアル。アドオンの開発をこれから始めたいという人向け。 68 14%
4章 アドオン開発経験者 Bootstrapped Extensions、ChromeWorker、e10sなど、Add-on SDKよりも下のレイヤの技術の解説。今既にアドオンを開発してるという人や、Add-on SDKではできない・標準ライブラリでカバーされてない範囲の事に手を出そうと思ってる人向け。 156 33%
5章 Webデベロッパー Firefoxで利用できるHTML5関連技術やECMAScriptの紹介。Webデベロッパー(主にフロントエンド)向け。 98 21%
6章 アドオン開発経験者、Webデベロッパー ハードウェア寄りの話と、その他のこぼれ話。アドオンやWebアプリの開発で、ネイティブアプリ並の事をやりたい人向けの話が多め。かも。 56 12%

自分の担当箇所だけ細かく見てみると、ページ数と全体に対する割合はこんな感じです。

  • Bootstrapped Extensions(再起動のいらないアドオン)の話:54ページ、11%
  • 非同期処理の紹介:72ページ、15%
    • そのうち、JSDeferredの紹介:43ページ、9%
  • js-ctypes(6章):27ページ、6%

調子に乗って脳汁ドバドバ出して書きまくる→ページ数が増える→値段上がる→損益分岐点も上がる という危険なコンボが発動してしまったためかお値段高めとなっておりますが、自分の担当箇所も他の方の担当箇所もひっくるめて、腰を据えて読むに値する情報ばかりなんじゃないかなーと思っております。Webで難なく読める文章の長さと、(紙でも電子でも)書籍の形になってた方が読みやすい文章の長さって、やっぱり違うと思いますしね。

本書のサポートサイトも公開されており、正誤表や本文中の各コードリストのダウンロード、本文の一部を切り出したサンプルPDFの無償公開などがあります。正誤表等はこれから更新されていくと思いますので、本書をお読みになられる際はこちらのサイトも併せてご参照いただければ幸いです。

以下は、思い出話です。

本書の企画がスタートしたのは、Firefox 4の正式版が出る前の2010年5月頃だったと思います(今メールボックスの一番古いメールの日付を見て確認した)。当時はまだ内部バージョンがFirefox 3.7とか言われていた頃で、Aero Glassが標準で入るのか?とか、そんな事を言ってた時期でした(という事も今Wikipediaを見て確認した)。

それからしばらく、どんな内容にするのがいいか? どんな人に執筆を依頼しようか?(今著者の一覧に名前が載ってる方の中には、最初の時点で名前が挙がってた著者の側から「是非この人にも書いてもらいたい!」と引っ張り込んだ人もいるのです) という事を話し合っていて、執筆者が確定したのが8月。そこからぼちぼち書き始めて、なんやかやで1年経ってしまいました(普通はここまで難産にはならないみたいです)。その間にFirefoxのバージョン番号もどんどん上がっていって、もうすぐFirefox 8ですよ奥さん! 時間が過ぎるのは本当に早いものですね。

最初の頃は、自分は「Firefox 3 Hacksで書いた内容をFriefox 4時点での情報に基づいてリライトするか?」程度の事を考えていました。でも、それじゃあ書いてる自分がつまらないし、それこそすぐに陳腐化してしまいます。それでうんうん唸りながら調べていくうちに、Firefox 4以降の話で新しい基盤の技術になりそうなトピックがいくつかあるという事が分かってきたので、それじゃあってんで、そこをターゲットにして書いていく事にしました。実は、自分の担当箇所は半年くらい前にほぼ完成してたんですよね。そこから本書の発売までの間は、(他の事に時間を使ってたからというのもありますが、)細かい記述のアップデート程度しかやってません。高速リリースになってからガンガン方針が変わったり実装が変わったりしてて、本書に書いた「テクニック」の中にも、既に「もうこんな回りくどい事しなくてもいいんだけどなぁ」な感じになってしまった話題がいくつかあるにはあるのですが(校正の詰めの詰めに入った段階でまたどどっと変更が入ってきて、どーしても間に合わなかったんです……)、全体としてはちゃんと今でも、そしてこれからも通用する話になってると思います。あの時の自分の目の付け所は、そんなには間違ってなかった、はず。

紆余曲折があった末にようやく世に出る事ができた本書ですが、アドオン開発者の方の手助けとなり、また、アドオン開発に手を出してみようと考えている方の手引きとなれば、幸いです。

他の著者の方々による本書の紹介のエントリ:

Page 1/248: 1 2 3 4 5 6 7 8 9 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき