Node をシェルスクリプト代わりに使う時、便利なもじゅおー

glob

glob "img/**/*.png", options, (filenames) ->

みたいな書き方ができる。


mkdirp

パス貫通する mkdir


gm

画像のリサイズとかクロップとか。
別途、GraphicsMagickのインストールが必要だけど、GPU使うから、ImageMagickより早い


request

HTTPリクエスト書くとき楽


yargs

コマンドライン引数扱うとき超便利

command --hoge fuga

(require "yargs").argv.hoge

で取れる。

手続きを意識させない設計をする

Twitter のツイート数を取得できる URL がある。

http://urls.api.twitter.com/1/urls/count.json?url=example.org

例えば、この URL からツイート数をコンソールに表示するプログラムを書きたいとする。coffeescript

愚直に書くとこんな感じ

JSON_URL = "http://urls.api.twitter.com/1/urls/count.json?url="
request = require "request"

request JSON_URL+"example.org", (err, res, body) ->
  data = JSON.parse body
  console.log data.count

これだと使いまわせないから、関数にする。

getTweetCount = (url, callback) ->
  request JSON_URL+url, (err, res, body) ->
    data = JSON.parse body
    callback data.count

getTweetCount "example.org", (count) ->
  console.log count

良さ気に見える。
でも、これだと、複数回よんだとき、
同じ URL でも何度も読みに行ってしまうから、良くない。

getTweetCount "example.org", (count) ->
  console.log "first", count
getTweetCount "example.org", (count) ->
  console.log "second", count

もちろん、これぐらい近い位置で呼ぶなら、
同じコールバックで括ればいいけど、そうも行かないことは、ままある。

ここで満を持して、クラスやイベントを活用する。
予め、load をしておいて、値を取得するときは、 get を使う

EventEmitter = (require "eventemitter2").EventEmitter2

class TweetCount extends EventEmitter
  constructor: (@url) ->
  load: =>
    request JSON_URL+@url, @onLoad
  onLoad: (err, res, body) =>
    data = JSON.parse body
    @count = data.count
    @emit "load"
  get: -> @count

tweetCount = new TweetCount "example.org"
tweetCount.load()
tweetCount.on "load", ->
  console.log "first", tweetCount.get()
tweetCount.on "load", ->
  console.log "second", tweetCount.get()

しかし、これだと、
load 前と load 後で、値の呼び方を変えなければいけない。
load 後は .on "load" が呼ばれないし、load 前は @count が定義されていない。 使うときには、TweetCount が既にロードされているかどうかを、常に意識しなければいけない。

そういう事のないように、
複雑な手続きを踏むインターフェイスは、
順番や状態を意識せずに使えるよう、善く設計する必要がある。

class TweetCount extends EventEmitter
  constructor: (@url) ->
    @loaded = false
    @loading = false

  get: (callback) =>
    return callback @count if @loaded
    @once "load", callback
    @load() unless @loading

  load: =>
    @loading = true
    request JSON_URL+@url, @onLoad

  onLoad: (err, res, body) =>
    data = JSON.parse body
    @count = data.count
    @loading = false
    @loaded = true
    @emit "load", @count

tweetCount = new TweetCount "example.org"

tweetCount.get (count) ->
  console.log "first", count
tweetCount.get (count) ->
  console.log "second", count

状態を吸収することで、
常に同じインターフェイスを利用できるように、設計をする。

co と jQuery 組み合わせて動くのでは

var co = require("co");

var $title = $("#title");
co(function*(){
  yield $title.animate({fontSize: 100}).promise();
  console.log("1");
  yield $title.animate({fontSize: 10}).promise();
  console.log("2");
  yield $title.animate({fontSize: 200}).promise();
  console.log("3");
  yield $title.animate({fontSize: 20}).promise();
  console.log("4");
  yield $title.animate({fontSize: 400}).promise();
  console.log("5");
  yield $title.animate({fontSize: 0}).promise();
  console.log("end");
});
6to5 --blacklist generators hoge.js > temp.js | browserify temp.js | regenerator --include-runtime > fuga.js

async 捨てられるのでは

我々の求めた ES6 Generators と Arrow functions

動くじゃないか!

let gen = function*(resume){
  let wait1000ms = () => setTimeout(resume, 1000);
  yield wait1000ms();
  document.write("Hello<br>");
  yield wait1000ms();
  document.write("ES6<br>");
  yield wait1000ms();
  document.write("Generators<br>");
  yield wait1000ms();
  document.write("Click Somewhere<br>");
  yield document.addEventListener("click", resume);
  document.write("Well done<br>");
};

let it = gen(x => it.next(x).value);
it.next();
6to5 --blacklist generators hoge.js | regenerator --include-runtime > fuga.js

なおlet