音の鳴るブログ

鳴らないこともある

JS クイズ

Q: 以下のテストをパスしてください

function assert(cond) {
  if (!cond) throw new Error("failed!!");
}

// Foo と Bar は異なる
assert(Foo !== Bar);

// new Foo() は Foo / Bar のインスタンスである
assert(new Foo() instanceof Foo);
assert(new Foo() instanceof Bar);

// new Bar() も Foo / Bar のインスタンスである
assert(new Bar() instanceof Foo);
assert(new Bar() instanceof Bar);

A: https://jsfiddle.net/oz2sfb56/

JavaScriptの小銭リテラルが便利

リテラルとは

コンピュータプログラムのソースコードなどで、特定のデータ型による値を直接表記する際の書式。また、そのような書式に従って記載された値。

JavaScriptにも数値や文字列、オブジェクト、正規表現などのリテラル表現があるのだけど、意外と知られていないものに 小銭リテラル がある。小銭リテラルはその名のとおり小銭を表現する書式で仕様では以下のように定義されている。

KozeniLiteral ::
  DecimalIntegerLiteral KozeniParts(opt)

KozeniParts ::
  KozeniPart
  KozeniParts KozeniPart

KozeniPart ::
  KozeniDelimiter KozeniDigits

KozeniDigits ::
  DecimalDigit DecimalDigit DecimalDigit

KozeniDelimiter ::
  ,

使用例

変数(price)には小銭の部分だけが代入されるので余計な演算をすることなく小銭を取得できる。

var price = 1,234,567; // 小銭リテラル

console.log("小銭は " + price + "円です");
// => 小銭は 567 円です

試しに数値リテラルで同じことをする例も載せるが複雑な演算子を必要としない小銭リテラルの方が明らかにシンプルで便利なのが分かると思う。

var price = 1234567; // 数値リテラル

console.log("小銭は " + (price % 1000) + "円です");
// => 小銭は 567 円です

このように便利な小銭リテラルだけど、エッジすぎる機能なのか残念なことに現時点ではきちんと実装されておらず非常にバギーなので、実装が安定するまでは使うのを控えた方がよさそう。

var price = 1,234,067; // 小銭リテラル

console.log("小銭は " + price + "円です");
// => 小銭は 55 円です (12円の損)

確認されているバグ的なやつ

  • 小銭が少なくなる場合がある
  • strict mode だと SyntaxError になる場合がある
  • 定義されていない書式でも動作することがある

参照

ECMA-262 11.14 コンマ演算子(,)

prominence - node.jsコールバックをPromise化するやつ

と、いうのを作りました。

node.jsの非同期APIはコールバックでエラーと結果を受け取りますが、それをPromiseベースのAPIに変換します。

こうやって使う。

var fs = require("fs");
var prominence = require("prominence");

// prominence するとそのオブジェクトで使える全メソッドが Promise 仕様になる
prominence(fs).readFile("hoge.txt", "utf-8").then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

// こういう書き方もできる
prominence(fs, "readFile", [ "hoge.txt", "utf-8" ]).then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

// 中でやっていること
new Promise(function(resolve, reject) {
  fs.readFile("hoge.txt", "utf-8", function(err, text) {
    if (err) {
      return reject(err);
    }
    return resolve(text);
  });
}).then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

prominence というのは凄く書きづらいですね。

類似のやつも紹介しておきます。

画像を点字にする

ごめんな。Windowsでは点字が表示できないんだ。

2年くらい前にこの記事を見て、真似してブラウザで動くやつを作ったのだけどすっかり忘れて放置していた。

僕の書いたのは二値化した単色点字で、元になったほうを改めて見るとクオリティが全然違っててビックリするのだけど、今週になってもうちょっと便利にならんか?という問い合わせをいただいたので、せっかくだしnpmモジュール化しました。(ブラウザのほうは修正するのが面倒そうだった)

こういう感じで使う。

$ seurat image/lena.jpg

f:id:mohayonao:20150310220043p:plain

他にサイズや二値化の具合を調整したり、白黒反転させたりできる。

$ seurat --help

Usage: seurat [options] path/to/image

  -w, --width Number      width(cols) of the converted text
  -h, --height Number     height(rows) of the converted text
  -t, --threshold Number  threshold for binarization
  -i, --invert            invert to negative
  -o, --output String     write the converted text to this file
  -p, --print             print out the converted text
  -v, --version           show version
  --help                  show help

勢いで作ってみたは良いものの問い合わせをくれたのは技術系の人ではなくて node.js が使えないとのことなのでどう連絡しようか悩んでいるところです。

ES6の書き方が定まらない

最近ちょっとしたコードは ES6 で書くようにしているのだけど、慣れていないせいか書き方が定まらない。

例えば ArrowFunction の => の前後にスペースを入れるかどうか

let foo = ()=>{};   // 最初はこうしていたけど
let foo = ()=> {};  // これを経て
let foo = () =>{};
let foo = () => {}; // 結局これになった

ArrowFunction の {} を省略するかどうか

let foo = array.map(x => x * 2); // 単純な演算なら省略する

let foo = array.map((x) => { // メソッド呼び出しが入っていたら
  return x.valueOf();        // {} は 省略しないようにしていたけど
});
let foo = array.map(x => x.valueOf()); // 結局は省略するようになった

// でも(感覚的に)長いときや入れ子になっているときは省略しない
let foo = array.map((x) => {
  return x.slice(10).concat(x.slice(0, 10).reverse().map(x => x.valueOf()));
});
// {} を使うときは引数の () も省略しない (これはまだ悩んでいる)

// 関係ないけど、よく括弧を書き忘れる
let a = aray.map x => x.clone();

export のタイミングとか

class Foo {}
export default Foo; // 最初はファイルの最後にこうしていたけど

export default class Foo {} // 良く考えたらこっちの方が良いと思う
export class Bar {}        // 上の書き方の場合、default 以外が書きにくくなる

上の書き方だとこういうインポートをする羽目になる

import Foo, {Bar} from "./foobar"; // なんか格好悪い

今、気が付いたけどこう書けばよかったのかも

export class Foo {} // export はその場で書く
export class Bar {}

export default Foo; // export default はファイルの最後に書く
// 格好良い
import Foo from "./foobar";
import { Foo, Bar } from "./foobar";

// こっちの方が分かりやすいのかも知れない..
import * as foobar from "./foobar";

あと var と let を使い分けたり..

let a = () => { // 一番外側は let
  var a = 10;   // 関数の最初は var
  {
    let a = 5;  // ブロックの中は let
  }
  return a;
};

// 今は var はいっさい使わない

本当に最初のときはすごく乱暴にこういう感じだった

export default new (class Bar extends require("./foo") {
})();

ES6、こういう感じで書くのが良いよというのがあったら教えてください!!

Promiseの使い方が良く分からなくなってきた

先に言っておくと疑問文で終わる雑な記事です。

Promise、正常系の処理は比較的簡単に書けるのだけど、何か間違いがあったときに Promise 自体のエラーハンドリングが強力すぎて良く分からなくなる。

readFile というファイルを読み込む Promise ベースの良くできた関数があるとして、

読み込んだファイルを処理するとき

readFile("foo.txt").then(function(text) {
  doSomething(text);
});

ファイルが読み込めない場合を想定するとき

readFile("not-found.txt").then(function(text) {
  doSomething(text);
}, function() {
  readFailed();
});

この時、catch を使うのは良くなくて、ファイルの読み込みは成功したのに readFailedが実行される場合が考えられる。

readFile("存在するファイル.txt").then(function(text) {
  doBrokenSomething(text); // ここでエラーが発生する
}).catch(function() {
  readFailed();
});

これを回避するにはこう書く。というか Promise ベースの API を使ったら最後に必ず catch しておく必要があると思う。

readFile("foo.txt").then(function(text) {
  doComplexSomething(text); // ここでエラーが発生するかも知れない
}, function() {
  readFailed();
}).catch(console.error.bind(console));
// ^^ doComplexSomething/ readFailed でエラーがあったとき

と、いうのが Promise の基本的な使い方だと思っているのだけど、、

これだとエラーがあったことは分かるけど、どこでエラー発生したか等の情報がいまいち分かりにくくて、デバッグしづらい。よくよく考えると Promise は非同期で実行されるフローを分かりやすく書くことが目的なので、すべてのおぜん立てが揃った最後の then の中で複雑な処理を書くのがそもそもの間違いな気がする。その段階では Promise のエラーハンドリング機能は邪魔でしかないと思う。Promise の呪縛から逃れるには 大きなスコープ を使うか setTimeout を使うというのが考えられるのだけど、本当にこんなので良いのでしょうか?

// 大きなスコープを使う
var text = null;

readFile("foo.txt").then(function(_text) {
  text = _text;
});

button.on("click", function() {
  if (text !== null) {
    doComplexSomething(text);
  } else {
    readFailed();
  }
});
// setTimeout を使う
button.on("click", function() {
  readFile("foo.txt").then(function(text) {
    setTimeout(function() {
      doComplexSomething(text);
    }, 0);
  }, function() {
    setTimeout(function() {
      readFailed();
    }, 0);
  });
});

追記:

ということをブツブツ書いていたら、有益な情報教えていただきました!!