Panda Noir

JavaScript の限界を究めるブログでした。最近は幅広めに書いてます。

ちょっとフックがあれば知ってるけど、意外と知らないJavaScript技術

JavaScript技術は10年前とは比較にならないほど膨大で緻密になっています。それらはアンテナを張っていなければ見落としてしまいます。今回はそんな技術の一端を紹介します。

紹介する技術たち

  • Web Worker + Service Worker
  • SharedArrayBuffer + Atomics API
  • manifest.json
  • WebRTC
  • WebSocket
  • SIMD.js
  • WebAssembly(wasm), asm.js

それぞれについて簡単に解説をしています。さらに深く知りたい方はおすすめ記事を参照ください。

続きを読む

隣接するマスへの差分の配列をワンライナーで生成する

初めに言っておくと、数式が覚えられないくらい複雑なので実用性はありません。これが簡単な数式だったらまだ使えたのですがね…普通に[[0,1],[1,0],[0,-1],[-1,0]]のように地道に書いたほうが早いです。

用途としては、forループ内で配列を使わないで生成ができるくらいです。

隣接マスまでの差分とは?

(x,y)にいたとして、(x-1,y)(x+1,y+1)のような座標のことです。8近傍は斜めを含み、4近傍は含みません。

ソースコード

console.log([...Array(9)].map((_, i) => [(i/3|0)-1,i%3-1])); // 8近傍(現在のマスを含む)
console.log([...Array(4)].map((_, i) => [(i-2)*(i%2),(i-1)*(1-i%2)])); // 4近傍(現在のマスは含まない)
console.log([...Array(5)].map((_, i) => [(i-2)*(i%2),(i-2)/2|0])); // 4近傍(現在のマスを含む)

以下のような配列が得られます。

[[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]]
[[0,-1],[-1,0],[0,1],[1,0]]
[[0,-1],[-1,0],[0,0],[1,0],[0,1]]

forと組み合わせて使う

for(let i = 0; i < 9; i++)
    const [dx, dy] = [(i/3|0)-1,i%3-1]; // 8近傍(現在のマスを含む)

for(let i = 0; i < 4; i++)
    const [dx, dy] = [(i-2)*(i%2),(i-1)*(1-i%2)]; // 4近傍(現在のマスは含まない)

for(let i = 0; i < 5; i++)
    const [dx, dy] = [(i-2)*(i%2),(i-2)/2|0]; // 4近傍(現在のマスを含む)

実用的なソース

console.log([[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]]);
console.log([[0,-1],[-1,0],[0,1],[1,0]]);
console.log([[0,-1],[-1,0],[0,0],[1,0],[0,1]]);
for(let i = 0; i < 9; i++)
    const [dx, dy] = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]][i]; // 8近傍(現在のマスを含む)

for(let i = 0; i < 4; i++)
    const [dx, dy] = [[0,-1],[-1,0],[0,1],[1,0]][i]; // 4近傍(現在のマスは含まない)

for(let i = 0; i < 5; i++)
    const [dx, dy] = [[0,-1],[-1,0],[0,0],[1,0],[0,1]][i]; // 4近傍(現在のマスを含む)

解説

8近傍はみたままなので解説をしません。トリッキーなことをしている残り2つを解説します。

4近傍で得たいもの

4近傍では以下のような配列を得たいです。

[[0,-1],
[-1,0],
[0,1],
[1,0]]

コレを見ると、偶数、奇数のところが0になっていることがわかります。というかそうなるように並べたんですが。

ということは[index%2,1-index%2]としてやればつぎのような配列が得られます。

[[0,1],
[1,0],
[0,1],
[1,0]]

あとはindexが2未満のとき-1、indexが2以上のとき1をかけてやれば完成ですね。ただ、こうなるよう数式をいじるのは意外とめんどくさいので以下で代用しました。

  • iが1のとき-1、iが3のとき1を得られるi-2
  • iが0のとき-1、iが2のとき1を得られるi-1

現在のマスを含むパターン

現在のマスを含むほうは得る順番を若干変えることで数式をカンタンにしています。

[[0,-1],
[-1,0],
[0,0],
[1,0],
[0,1]]

真ん中が[0,1]から[0,0]になっています。こうすると、y座標の差分がカンタンに求めることができるようになります。

graphql-go入門

graphql-goの入門記事が全く出てこなくて苦しみ悶えながらようやく理解できたので、ここに戦いの記録を残しておこうと思います。

今回の環境

今回はMongoDB+GraphQLという構成にしました。

成果物をここにまとめましたので、参照ください。 github.com

GraphQLとは?

そもそもGraphQLとはナニカ、ですがGraphQLはクエリ言語で、APIサーバーで使われます。

たとえばつぎのようなリクエストを送ってみます。

query {
    shop(name: "ラビットハウス") {
        name
        members {
            name
        }
    }
}

すると以下のようなデータが返ってきます。

{
    "data": {
        "shop": {
            "name": "ラビットハウス",
            "members": [
                {"age":16,"name":"保登心愛"},
                {"age":14,"name":"香風智乃"},
                {"age":17,"name":"天々座理世"}
            ]}
    }
}
続きを読む

Nodeで外部のwebサーバーを建ててkillするまで

NodeでGoで書いたwebサーバーのテストをしている(地獄)のですが、child_processが孫プロセスをうまくkillできなかったので、ゴリ押しでkillする手法を考えました。

孫プロセスのPIDを取得する

孫プロセスのPIDを取得できれば、killすることはカンタンにできます。

process.kill(mago_pid);

ということで、親子共々殺したい - Qiitaを参考に書いてみます。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);
// サーバーのPIDはserver.pidで取得できる
child_process.exec(`ps --ppid=${server.pid} | cut -d' ' -f1`, (error, stdout, stderr) => {
    if (error)
        throw new Error(error);
    console.log(stdout, stderr);
});

孫プロセスをkillする

あとはくっつけてやれば完成です。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await new Promise((resolve, reject) => {
        child_process.exec(`ps --ppid=${server.pid}`, (error, stdout, stderr) => {
            if (error)
                reject(error);
            resolve({stdout, stderr});
        })
    });
    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

util.promisifyを使う

util.promisifyべんりなのでドンドン使いましょう

const util = require('util');
const child_process = require('child_process'), exec = util.promisify(child_process.exec).bind(util);
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await exec(`ps --ppid=${server.pid}`);

    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

ネットワークでよく出てくる「コネクション」ってなんなんだろう?

ネットワークにおいて「コネクション」という単語はよく耳にします。

  • 「WebSocketはコネクションを確立後、ずっとそのコネクションを使う」
  • 「ロングポーリングは返すたびにクライアントがコネクションを確立し直す」
  • 「TCPでは3ウェイハンドシェイクでコネクションを確立する」

これらを見ると「コネクションは通信経路のことなのかな?」と勘違いしてしまいます。今回はそんな誤解を解いていこうと思います。

コネクションはネットワーク上の経路のことではない

TCPとUDPはそれぞれ

  • コネクション型プロトコル
  • コネクションレス型プロトコル

と呼ばれます。この2つを例にとって、コネクションについて説明していきたいと思います。

TCPは「通信をはじめるよ」「いいよ」というように通信できるか確認してから通信を行います。それに対し、UDPでは「送るよ。返事はいらないよ」と相手の状態を確認することなく無造作に送り続けます。

UDPもTCPも、ルーターを中継しながらデータを届けています。しかし、経路は固定ではありません。ルーターがあるのに経路を固定していたら何のためのルーターなのかわからなくなりますからね。つまりコネクション型でも、コネクションレス型でも経路は固定されていません。

(余談ですが、経路を固定するコネクション型通信も存在します)

コネクション型とコネクションレス型

TCPは通信を開始する際、相手が通信可能であることを確認してから通信をはじめます。また、一度通信を始めたらコネクションを解放するまではお互いに通信状態を維持します。それに対してUDPでは相手が通信可能状態なのか確認せず、ひたすら投げます。

ここがコネクション型とコネクションレス型のちがいです。通信経路を確保する(=相手と通信ができることを保証する)かどうかが異なっています。つまり「コネクションを確立する」というのは「お互いに通信状態になる」ということです。片一方が勝手にやめることはできません。

様々なコネクション

HTTPでは、クライアントがリクエストするときにコネクションを確立します。そして、サーバーがレスポンスを返すとコネクションを切断します。

ロングポーリングもHTTPと同様に、コネクションをリクエスト時に確立し、レスポンスを返すときに切断します。そのため、レスポンスを返した後は再度コネクションを確立する必要が有ります。

WebSocketではコネクションを確立したあとはずっとそれを使い通信を行います。サーバーがレスポンスを返しても切断をしません。