JavaScriptでバイナリをいじる方法を考える(決定打はない)

 ブラウザでFileAPIが実装され、Node.jsでもファイルのIOができるようになって久しい。JSで書いたライブラリでバイナリを扱っているので、その方法を数年ぶりに考察してみる。

 まずFileAPIで返ってくるDataURI。これはASCIIでバイナリを表示できるようにと考え用意されたもの。JSでの型はstring。
 ブラウザではこの形式で画像を読み込んでくれる。しかしASCII化するためにデータ量が30%以上膨れ上がり、何バイト目のデータはいくつかという値取得などの操作も難しい。バイナリを操作するのに向いていない。

 UInt8Array。まさにバイナリを操作するのに実装された型。メモリ上でのデータの扱いも合理的になっている。1バイトを符号なしのショートの整数として扱える。
 ただいまいち任意の場所のデータ比較がいけてると思えない。Pythonならこう
data[0:2] == b'\xff\xda'



 JSでは…配列同士の比較ではダメかっていうとダメ。配列の等価比較を簡単にやることができない。
"=="をArray系にやる→オブジェクトの比較になる→オブジェクトが同一かを比較される、オブジェクトの値ではなく。
 ↓のはfalse。
data.subarray(0,2) == new UInt8Array([255, 0]);



JSではいちいち切り出して、それを比較しやすいstring型に変更して、という手順をふまなきゃいけない。
String.fromCharCode.apply("", data.subarray(0, 2)) === "\xff\xda"




 それがいやならUInt8Arrayではなくstringを使っておくとか。
data.substring(0, 2) == "\xff\x00";



 ただしstringの弱点は、console.logで表示したときにUnicodeで表示されてしまうこと。そのままではデバッグが困難。
let foo = "\xff\x00";

console.log(foo); // ÿ�


 UInt8Arrayの部分比較がもっと楽だったらなー。そういうわけで個人的に決定打はなかった。
comment: 0

JavaScriptでasync/awaitに対応していない関数を対応させる

***************************************************
JavaScriptにコールバックの濫用が存在し、それがこのプログラミングのただなかに人為によって地獄をつくりあげ、神聖な安定性を不幸でもつれさせるかぎり-、またこの言語の三つの問題、すなわち、コールバックであるためにエラー処理が複雑化し、深いネストのために可読性が堕落し、エラーメッセージがよくわからないためにプログラマがいじけるという三つの問題が解決されないかぎり、-また、あるライブラリがasync/await対応によって有用性が増す可能性のあるかぎり、-言葉をかえれば、そしてもっとひろい見方をすれば、この地上に平易さを失った書き方の処理があるかぎり、このような性質の記事もまた役に立たなくはないだろう"

「レ・ミゼラブル」 - ヴィクトル・ユゴーより(嘘)

***************************************************

 Nodejsでサーバサイドをごにょごにょと書いている。XMLをJSオブジェクトにしたくてxml2js
というライブラリを入れたが、async/await対応していなかったっぽい。これのおかげで処理に一つコールバックが入ってしまった。これをasync/await対応して隠ぺいするお話。というかawaitできるようにするというところ。それをxml2jsを例にして書いていく。

 まず従来のコールバックなxml2jsの使い方。
parseString(xml, options, function (err, obj) {

if (err) {
throw err;
}

var items = [];

obj["rdf:RDF"].item.forEach(function (item, index, array) {
let doc = {
_id: item["sec:identifier"],
title: item["title"],
desc: item["description"],
link: item["link"],
issued: item["dcterms:issued"],
modified: item["dcterms:modified"],
};
items.push(doc);
});
});


 MongoDBにデータを入れたいのでXMLをオブジェクトにパースしたいのだけど、このまま書くならオブジェクト取得後の処理(加工、保存など)をコールバック関数の中に書かなければならない。parseStringをawaitして、オブジェクトを返してもらいたい。どうすればawaitを適用できる関数となるか。
 Promiseオブジェクトにしてしまう。これはちょっと進めると、Promiseオブジェクトを返す関数を書くのが一般的な様子。
function asyncParseString(xml) {

return new Promise(function (resolve, reject) {
const options = {
trim: true,
explicitArray: false
};
parseString(xml, options, function (err, obj) {
if (err) {
reject(err);
}

resolve(obj);
});
});
}

 resolveの引数で成功時の返り値を、rejectの引数でエラー時の値を渡す。
 あとはこの関数をawaitすればOK。
let obj = await asyncParseString(xml);

 受け取ったobjをこのあとごにょごにょしていけばいい。

まとめ
・async/await対応させたいライブラリがあったら、Promiseオブジェクトを使え

'18/2/24
async/awaitとPromiseがブラウザでも使えることを確認したのでタイトルを修正。XMLHttpRequestとかをこれでasync/await対応させてる話とかもある。

冒頭の文の元ネタ。
***************************************************
title: レ・ミゼラブル (1) (新潮文庫)
authors: ユゴー
社会に法律と風習による処罰が存在し、それがこの文明のただなかに人為によって地獄をつくりあげ、神聖な運命を人間の不幸でもつれさせるかぎり-、またこの世紀の三つの問題、すなわち、プロレタリアであるために男が落伍し、飢えのために女が堕落し、闇夜のために子どもがいじけるという三つの問題が解決されないかぎり、-また、ある地域で社会の窒息状態が生じる可能性のあるかぎり、-言葉をかえれば、そしてもっとひろい見方をすれば、この地上に無知と悲惨とがあるかぎり、このような性質の本もまた役に立たなくはないだろう。

***************************************************
comment: 0

C#でMongoDBでのサーバーサイドJavaScriptを実行する

 コレクションの集計処理をしたかったのだが、MapReduceでは望みの形に集計結果をまとめられなかった。だからサーバーサイドJavaScriptでやることにした。ただしサーバーサイドJavaScriptはセキュリティの懸念やパフォーマンスの注意点があるので気軽に使うものではない。今回はそれらが問題として出てこないので使うことにした。

 まずC#コード。サーバーサイドで実行するJavaScriptも埋め込んだ。
//using MongoDB.Driver;

//using MongoDB.Bson;
//using MongoDB.Driver.Core.Operations;
//using MongoDB.Driver.Core.WireProtocol.Messages.Encoders;
//using MongoDB.Driver.Core.Bindings;
//using System.Threading;

class Program
{
private static IMongoClient client;
private static IMongoDatabase DbConnection;

static void Main(string[] args)
{
client = new MongoClient("mongodb://127.0.0.1");
DbConnection = client.GetDatabase("helicon");

AggregateTag();
}

public static void AggregateTag()
{
var databaseName = new DatabaseNamespace("helicon");

var code = (BsonJavaScript)@"
function () {
// do something
}";

var messageEncodingSettings = new MessageEncoderSettings();
var operation = new EvalOperation(databaseName, code, messageEncodingSettings);
var source = new CancellationTokenSource();
var token = source.Token;
var writeBinding = new WritableServerBinding(client.Cluster);
operation.Execute(writeBinding, CancellationToken.None);
}
}


 あとはサーバサイドでJavaScriptを実行するために、接続ユーザに実行権限を持たせる。
Allow user to execute eval() command on MongoDB 3.x

データベースに管理権限者でログイン。そしてadminデータベースへ。
use admin;


ロールを作成。
db.createRole( { role: "executeFunctions", privileges: [ { resource: { anyResource: true }, actions: [ "anyAction" ] } ], roles: [] } )


ロールを接続ユーザに付加。
db.grantRolesToUser("someone", [ { role: "executeFunctions", db: "admin" } ])


付加されたことを確認。
db.getUser("someone") 


これで権限が付いたので、C#コードを実行すればMongoDBのサーバサイドJSが実行される。

 このブログのサイドバーの、タグや投稿月毎の記事のカウントにこの、MongoDBのサーバサイドJSを使っている。
comment: 0

JavaScriptで辞書型のキーをintだと思うな

 ぼくのJSのOSSライブラリにバグが報告されたので調べていたら、タイトルのような結論が出た。「辞書型」を「連想配列」に置き換えたい人は置き換えてもらっても構わないけど、本当はなにより「オブジェクト」と書きたい。というわけで以下オブジェクトと書く。

 JSのオブジェクトのキーは、intでキーを入れても、そのキーはstringに変換される。つまり…

オブジェクトのキーに整数1があるか調べたい。
Object.keys({1:"a"}).indexOf(1);

↑ "-1"

Object.keys({1:"a"}).indexOf("1");

↑ "0"

 でも下のようなのはキャストが効くのかありだったりするみたい。
1 in {1:"a"}


 上記はブラウザではもちろんNode.jsでも。
 JSのオブジェクトのキーが整数型を使えると思っていたらいかん。千行ぐらいのライブラリを書いているのにまったく知らんかった。
comment: 0

JavaScriptで文字列フォーマット

 アドベントカレンダーやってみたけどネタを生み出す時間が平日はなかなかきつい。今日も短めに。

 進化のスピードが早いJavaScript。文字列表現に変数を埋め込む方法が以前までなかったのだが、ぼくが気づかぬうちに追加されていた。

 文字列をバックティック「`」で囲む。バックティックであってシングルクォートではない。バックティックであってBUCK-TICKではない。
バックティックで囲んだら他言語と同じように、文字列中で変数名を「${}」で囲う。

var s = "foo";

console.log(`${s} bar`);
comment: 0