(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

JavaScript の Promise を返す関数を直列で実行したいので Pinscher というライブラリを作ってみた。

github.com

README すらまだ書いてないけど、とりあえず使えるものができたので npm にだけ登録してみた。

追記

javascripter.hatenablog.com
3 秒で不要になった。 Promise を直列で実行するのに、 Promise それ自身の特徴を使えば簡単にできることに気付かなかった。 reduce を reduce としてしかみてなく本質をみれば気付けたのに気付けなかったので修行足りてない。

何をするライブラリを作ったのか

簡単に言えば Promise を返す関数を順序を守って実行するものである。まぁ queue をためて前の関数の実行が終わったら次の関数を実行する、といったようなもの。

何故必要だったのか、あるいは作ったモチベーション

仕事をしていて AngularJS を使っているんだけど、 $http サービスの返す Promise をキャッシュしたら実行速度あがるんじゃないかと思ったんだけど、 Promise をキャッシュするとキャッシュありとなしで実行に差が出てしまい後発で実行したのに先に結果が返ってきて順序が守られないという、非同期なんだから当たり前なんだけどそういう当たり前な問題にぶつかってどうしても順序を守らせたくなったので作ることにした。
事前に計算順序を守る必要がある Promise のタスクを直列化する方法はいろんなところに書いてあるんだけど*1、今回やりたかったのは「全く同じ関数が何度も呼び出されその結果を画面に描画するため順序を守る必要がある」というちょっと趣旨が違う要件だったので自分で実装してしまった。

つまり、こういう実装をしたときに

var memoizedFunc = _.memoize(someFunc);

memoizedFunc('Hello').then(console.log);
memoizedFunc('ayato_p').then(console.log);
memoizedFunc('zer0_u').then(console.log);
memoizedFunc('alea12').then(console.log);
memoizedFunc('Hello').then(console.log);

function someFunc(key){
  return new Promise(function(resolve, reject){
    setTimeout(function(){ resolve('***' + key + '***'); }, 1000);
  });
}

普通はこういう結果が得られるんだけど

***Hello***
***Hello***
***ayato_p***
***zer0_u***
***alea12***

こうしたいということ

***hello***
***ayato_p***
***zer0_u***
***alea12***
***hello***

需要があるかはともかく簡単に検索した感じだとなさそうだったので、いいかなーって思って作っちゃった。

使い方

インストールは npm でするっと

% npm install pinscher

実際のコードだとこんな感じ。

var _ = require('underscore'),
    Promise = require('ypromise'),
    Pinscher = require('pinscher'),
    pinscher = new Pinscher(),
    memoizedFunc = _.memoize(someFunc);

pinscher.run(memoizedFunc.bind(null, 'hello')).then(console.log);
pinscher.run(memoizedFunc.bind(null, 'ayato_p')).then(console.log);
pinscher.run(memoizedFunc.bind(null, 'zer0_u')).then(console.log);
pinscher.run(memoizedFunc.bind(null, 'alea12')).then(console.log);
pinscher.run(memoizedFunc.bind(null, 'hello')).then(console.log);

function someFunc(key){
  return new Promise(function(resolve, reject){
    setTimeout(function(){ resolve('***' + key + '***'); }, 1000);
  });
}

new でインスタンス作ってあげて run 関数に実行したい Promise を返すような関数を渡してあげるだけ。
こうすると結果が期待どおりになる。

***hello***
***ayato_p***
***zer0_u***
***alea12***
***hello***

今後やりたいこと

とりあえず当初目的は達成出来たのでいいんだけど、 README もまともにないので書きたい*2。あとテスト書いてないので書きたい*3
それから Promise を折角使ってるのに直列で実行してしまっているので、多少速度が遅くなる*4ので実行だけは素直にするんだけど結果を返すところだけを直列にするとかしたい。それと CommonJS 形式の require にしか対応してなくてブラウザで使えるようにしてないのでちょっとあとでやる。

感想

普段の仕事だと AngularJS なので $q を使っているんですが、今回は生の ES の Promise 実装に近い方がいいやと思って ypromise を使ったのでちょっと苦労しました。 q でいうあれってどうやるんだ?みたいなのを悩んだりしてました。 Promise の本にはいつもお世話になってます。azu.github.io

余談(実装について)

Promise を返す関数を渡すといいつつ、 then 関数をもったオブジェクト*5を返せば良いです。または返さなくても自動的に Promise を返すようにするので問題ないです。

余談(ライブラリの名前について)

Pinscher というのはドーベルマン系の犬種のことです。
Pinscher - Wikipedia, the free encyclopedia
ライブラリ作ったんだし適当に名前つけようと思ったんだけど、安直すぎる名前はだいたい使われているのでいいやって思って好きな犬の名前にしようと思って最初 Doberman だったんだけど、ちょっと名前が厳つすぎるなって思って DobermanDoberman Pinscher だと知ったので Pinscher にした感じ。
ちなみに実家にミニピンが二匹いますが、とても可愛いです。

*1:要は then で繋ぐのを reduce で書くとかそういう

*2:ブログは書くのにな、 README は英語で書きたいのであとで推敲しながらゆっくり書く

*3:けど、実行順序を守っているかってテスト書き難い気がしていてちょっとアレ。正確には「期待通りの順番で結果が返ってきているか」というのがテストなので難しそうである

*4:その分キャッシュされているものにヒットしたら早いんだけど

*5:thenable な