fetch API から XMLHttpRequest への置き換えを決意した話

最近 fetch API をヘビーに使うようになっていて、いろいろと勘所もわかってきていて、Promise ベースなのはやっぱりすごく便利なんだけれども、現状だと機能が全然足りないなあ、と。

XMLHttpRequestUpload 相当がないのは知っていたし、困ったなあと思っていたんだけれども、XMLHttpRequestUpload 自体がだいぶレア目のヤツで使うような機会もまあめったにないので実害としてはそこまで大きくなかった。

んで、だ、XMLHttpRequestUpload 相当がないのは良いとしても、ReadableStreamXMLHttpRequest で言う progress イベント相当のことをしようとしたときに、発火時にトータルの容量がわからんつう問題が発生した。

fetch API で ReadableStream を使って progress の状況を取るときは

function consume(reader) {
var total = 0;
return (function pump() {
return reader.read().then(function(args) {
if (!args.done) {
total += args.value.byteLength;
return pump();
}
});
})();
};
fetch('/path/to').then(function(response) {
var reader = response.body.getReader();
return consume(reader);
});

といったコードを使う。データを受信するごとに total 変数に、受信したデータのバイト数を追加していくことしかできない。

forbidden headers の中に Content-Length があるからしゃあないちゃしゃあないような気もするんだけれども、どうしたもんかまじでわからん。

トータルの容量がわからんとなにが困るって、進捗の表示ができないんだよ。

xhr.onprogress = function(e) {
result.textContent = e.loaded / e.total;
};

ていうので XMLHttpRequest だったら一瞬で進捗状況を表示させられていたけど、fetch API だとできない。いや仕様的には [[totalQueuedBytes]] つうのがあるんだけれども internal slot 扱いで JavaScript から取ることはできない。

これ、誰も困っていないのかな。困るよなあ。困るよなあ。どうすんのまじでコレ。

っていうのと今日実装している最中であーってなったのは XMLHttpRequest.abort() 相当のものがないってこと。まじでどうすんだよこれ!!! 今週の土日で今書いているプロダクトで fetch API を使っている箇所を XMLHttpRequest に置き換えるわ。やっぱり世界に fetch API は早かった。XMLHttpRequest ですよ、やっぱり。

XMLHttpRequest 最高!!!

……

という話でおわるのも酷いので補足をしますが、ちゃんと要所を抑えて使うと fetch API は非常に便利です。

Promise ベースというのはイベントベースの API よりもはるかに記述が単純で済みます。コードが煩雑になってしまうのを防ぎますし、この記事を書いている 2016 年 7 月時点では stage 3 の Async Functions (ES 2017 にはいるとうれしいですね) といっしょに使うことによって、コールバックファンクションばかりになってしまうということも避けられます。

今後も Promise ベースの流れが続くのが幸いだと思いつつも、現状の fetch API は「つらいなあ」というお話です。そもそも fetch API は XMLHttpRequest の代替というわけではないので、お門違いも甚しいんですけどね。

fetch API は Living Standard です。今後も改良が続けられていきます。この記事で上げている XMLHttpRequest との差異もいづれ埋められて行くでしょう。わたしはこの記事が陳腐化してしまうことを切に祈っています。

世界がもっと平和になりますように。

2016 年 7 月 21 日 20 時 30 分 追記

Jxck さんのご指摘を受けて再調査したところ、Content-Length ヘッダーの取得が行えました。forbidden headers というのはあくまで HTTP リクエスト送信時のみで、HTTP レスポンスを受ける際には関係ありませんでした。

通信の進捗状況を得つつ、かつレスポンスの値を得る場合には

function open(blob) {
return new Promise(function(resolve, reject) {
var fileReader = new FileReader();
fileReader.addEventListener('load', function() {
resolve(this.result);
});
fileReader.addEventListener('error', function() {
reject(this.error);
});
fileReader.readAsText(blob);
});
}
fetch('/object.json').then(function(response) {
var reader = response.body.getReader();
var type = response.headers.get('content-type') || 'text/plain';
var total = +(response.headers.get('content-length') || 0);
var loaded = 0;
var body = new Uint8Array(total);
return (function pump() {
return reader.read().then(function(args) {
var newBody;
if (args.done) {
return new Blob([body], { type: type });
}
if (total < 0) {
body.set(args.value, loaded);
} else {
newBody = new Uint8Array(body.byteLength + args.value.byteLength);
newBody.set(body);
newBody.set(args.value, body.byteLength);
body = newBody;
}
loaded += args.value.byteLength;
console.log('loaded: ' + loaded + (total < 0 ? ' (' + Math.floor(loaded / total * 1000) / 10 + '%)' : ''));
return pump();
});
})();
}).then(function(blob) {
return open(blob).then(function(text) {
return JSON.parse(text);
});
}).then(function(object) {
console.log(object);
});

といった記述になります。

Streams の仕様についてまだ理解が浅いため、もう少しスマートな書きかたはあるかとは思いますが、わたしが目的としていたことは実現できました。

また XMLHttpRequest.prototype.abort に関しても、ReadableStream.prototype.cancel で実現できそうです。仕様としてはストリームのキャンセルとともに HTTP リクエスト自体も止めると規定されているため、XMLHttpRequest.prototype.abort 相当のことはできそうです。

ただし実際に Google Chrome 52 で試してみたところストリームの読み取り自体は止まっているものの、HTTP リクエスト自体は止まっていないのではないか? という疑念があるのでもう少し調査する必要がありそうです。

なお response.body.getReader() は現状 Google Chrome 以外では実装されていない (MS Edge では使えるかも。未確認) ため、実用に耐えるかと言われたら疑問が残るかもしれません。

しかし、Fetch API も Streams もどちらも今なお活発に仕様の策定が進んでいます。それにともなって実装も広く進められていくことでしょう。

世界の平和は近い。

また、この追記は Jxck さんのお力によるものが大きいです。仕様に対して曖昧な理解のままでいたわたしに対して、正しい情報の教示をしてくださいました。非常に助かりました。ありがとうございます!

nginx の設定ファイルで正規表現を使って www. なしのドメイン名にリダイレクトさせる

一つのサーバーに複数のドメイン名のウェブページを同居させることはよくある話だと思います。負荷のことを思えばドメイン名単位でサーバーを分けたほうが良いのでしょうが、さしたアクセスが見込めないウェブページを集約しても問題は起きないでしょう。

ただ www. つきでアクセスされた際に、www. なしのドメイン名へのリダイレクトをさせる処理をドメイン名単位で一つ一つ書いていくのは無駄です。

nginx の server_name ディレクティブでは正規表現を使えます。nginx が正規表現エンジンとして使っている PCRE では名前付きキャプチャに対応しているので次のように書けます。

server {
listen 80;
server_name "~^www\.(?&lt;naked_domain&gt;.*)$";
set $proto $scheme;
if ($http_x_forwarded_proto = "https") {
set $proto $http_x_forwarded_proto;
}
if ($http_cloudfront_forwarded_proto = "https") {
set $proto $http_cloudfront_forwarded_proto;
}
rewrite . $proto://$naked_domain$uri permanent;
}

上位に CloudFront や CloudFlare といった CDN が存在しない場合は、$proto 周りの小細工が不要になり、もう少し単純になります。nginx の設定ファイルは簡単に書けて良いですね。

正規表現を使うと処理が一回の接続ごとに実行される処理が増えます。そのため速度の面を考えると使わないほうが安心ではあります。ですがそこまで気にする必要がないような場合は、nginx.conf の記述を簡略化できるので便利です。

ぜひご活用くださいませ。

エンジニアの採用でGitHubのアカウントの提出を求めることに関して

近年 エンジニアの採用においてGitHubのアカウントの提出を求められる企業が増えつつあります。それに対して採用の場で個人的な活動の結果を求めるのかと憤る人もいます。

たとえばデザイナーの採用でポートフォリオの提出が必須とされていることは多くあります。それと同様にエンジニアの採用でGitHubのアカウントとそこから見ることのできる成果物の提出を求めることに対する違和感はないのではないかと思います。

GitHubではなく、Bitbucketでも構いません。公開されていなくても良いでしょう。USBフラッシュメモリーやCDなどの光学メディアに入れられたソースコードでも問題ないでしょう。

エンジニアは一部の例外を除けば守秘義務の元で開発をしていることが大半です。ソースコードは基本的に外から見られません。なのでGitHubといった場で公開されているものがあると助かるのです。

ただなにも見えない状態で「学生時代 (前職で) は××のようなアプリケーションを作成していました」と言われても評価は難しいのでどのようなものでも見えると良いのです。

もちろん面接官が適切な質問をすることによって、評価の材料を集めることは可能でしょう。ですが評価するための素材が多いに越したことはありません。

個人的な活動を求めることに対して、否定的な意見を言う人の気持ちも理解できなくはありません。ですが、わたし個人の意見としてはGitHubのアカウントを持っていてくれるほうがうれしいです。

株式会社アニメイトラボにジョインしました

2015年11月1日付けで株式会社アニメイトラボにジョインしました。

社員としての初出社日は本日二日ですが、しばらく前からアニメイトラボのオフィスで働いていたため、実感というものは全くありません。なお籍も席もまだピクシブ株式会社に残っており、退職ではありません。なので例の画像 (わからない人はわからなくて構いません) は張りませんので、あしからずご了承ください。 アニメイトラボはその名の通りアニメイトグループに属する会社です。わたし自身のアニメイトに関する思い出や想いを書くと非常に長くなってしまうので省略しますが、わたしはアニメやマンガが好きで、何年も前からアニメイトでの商品の購入をしていました。なので、まさか自分がアニメイトグループの一員になる日が来るとは夢にも思っていませんでした。

わたしが直近行うのはアニメイトラボによるスマートフォン向けアプリの開発と運用になります。スマートフォン向けアプリの開発とは言いますがSwiftやObjective-Cを扱うというわけではなく、わたしはサーバーサイドのAPIの開発を行います。またインフラ面の仕様策定や運用についても行い、具体的に言うとアプリ開発以外のすべての技術的要素をわたしが担うことになります。

インフラ周りのことは以前より趣味程度ではあるものの、経験はしていましたが実務として多くのユーザーを抱えるのは始めてのこととなります。毎日毎日が勉強になることばかりで全てが新鮮です。

恒例のものとなりますが、わたしのAmazon Wishlistはこちらになります。皆さま、なにとぞよろしくおねがいいたします。

アニメイトラボはまだ設立されてから日の浅い会社です。そのためエンジニアの絶対数もまだ少ない状況です。アニメイトラボはインフラエンジニア、フロントエンドエンジニア、スマートフォン向けアプリエンジニア、その他もろもろ多種多様のエンジニアを求めております。多少でも興味を持たれましたら株式会社アニメイトラボ 採用サイトからぜひエントリーしてください。

転職しました

去年の末ごろからピクシブ株式会社で働いています。

当初の予定かんがえではキリが良く四月一日に長めの記事を書くつもりだったのですが、いろいろとあって記事を書く余裕がなく、このままずるずる延ばしても忘れるだけなので、とりあえずの記事を上げます。

Amazon Whishlist はこちらにまとめてありますので、お手隙の方は気軽に物品の贈答をしてくださりますとさいわいに思います。