ES2017でオブジェクトの全プロパティ値を得るには Object.values が使えるのに

ジブン向けに書いています。保守的にもほどがあるっちゅう話です。

JavaScriptでプログラミングしていると、 オブジェクトのプロパティの値の配列を取り出したい ってことがしょっちゅうあります。

現在最新の ECMAScript 2017 では、 オブジェクトの全プロパティの値を配列として得るために、Object.valuesが使えるのですが、ワタシは従来(ES2015以降)の方法に手がなじんでしまっており、いざという時に「実績ないからなー」的な不安感から使ってこなかったんですね。これって、どこかで無理やりこじ開けないとイケナイやつです。

従来 Object.keys を使って以下のようにしていました。長いですね。

let values = Object.keys(obj).map(function(key) { return obj[key];});

ECMAScript 2015では、アロー関数が使えるので、以下のようにさらに簡潔に書けます。

let values = Object.keys(obj).map(key => obj[key]);

しかし今やECMAScript 2017では、 Object.value を使って、以下のように書けるのです。腱鞘炎すら治りそうな勢いですよ。

let values = Object.values(obj);

どんどん Object.values を使いましょう > ジブン

テンプレートリテラルの展開時のエラーについて調べてみた

JavaScript で、テンプレートリテラルからエラーが投入される状況を確認してみました。

基本は、Node.js 8.10 LTS を使用しましたが、一部ChromeのJavaScriptでも確認しています。

結果として確認できたのは、下記2点。

  1. 定義済みオブジェクトの未定義キーを参照してもエラーにならない
  2. 未定義変数や未定義オブジェクト(=undefined)のキーを参照するとエラー

上記動作はJavaScript的に普通の動き。strictモードでも非strictモードでも同様です。「テンプレートリテラルだから」と特別なことはないようです。

以降、それぞれのケースを確認した結果です。

定義済みオブジェクトの未定義キーを参照する

定義済みオブジェクトの未定義キーを参照しても、エラーは投入されずundefinedと評価されます。

"use strict";
try {
    let foo = { bar:0 };
    console.log(`${foo.baz}`);//bazは未定義
    //出力>undefined
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
}

未定義変数を参照する

未定義の変数を参照すると、実行時エラーReferenceErrorが投入されます。

try {
    console.log(`${foo}`);//fooは未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>ReferenceError: foo is not defined
}

※ ブラウザでは以下のようにすればエラーになりません。ブラウザのJavaScriptのグローバル変数は window オブジェクトのキーとして定義されるためですね。

    console.log(`${window.foo}`);
    //出力>undefined

未定義オブジェクトのキーを(無理やり)参照

未定義オブジェクト(=undefined)のキーを参照しようとするとTypeErrorが投入されます。

try {
    let foo = {};
    console.log(`${foo.bar.baz}`);//barが未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: Cannot read property 'baz' of undefined
}

未定義の関数を呼び出す

定義済みオブジェクトの未定義のキーを関数呼び出しするとTypeErrorが投入されます。

try {
    let foo = { bar:0 };
    console.log(`${foo.baz()}`);//bazは未定義
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: foo.baz is not a function
}

関数以外を関数呼び出し

定義済みオブジェクトの関数でないメンバを呼び出そうとすると、TypeErrorが投入されます。

try {
    let foo = { bar:0 };
    console.log(`${foo.bar()}`);//barは関数ではない
} catch(err) {
    console.error(`${err.name}: ${err.message}`);
    //出力>TypeError: foo.bar is not a function
}

Gitサブモジュールを移動する一番簡単な方法は?

少なくとも私の環境 Git for Windows 2.7.0-windows.1 で、Git のサブモジュールのパスを移動するには、おなじみ git mv だけで事足りてます。

昔は大変だったんだよね

Gitのサブモジュールは扱いにくいと言われてきました。

場所を移動したり、削除したりする時、手操作でいろいろやらないとリポジトリが変な状態になってしまったりして困ってました。

今回、サブモジュールを別のディレクトリに移す必要が生じて極度の緊張。
怖れをなしてインターネッツで検索すると、それ見たことか「いろんなファイルをいじり倒さないといけません」と。

直感的には git mv でお願いしたい

その手順があまりにややこしくてげんなりしました。

「直感的には git mv なんだがなあ・・・」と愚痴が出たので、モノは試しとやってみたら大成功。

Gitのversionは、、、

$ git version
git version 2.17.0.windows.1

最近のGitでは改善されてたようですね。

いつから有効なのかは不明です。
少なくとも最新版にすれば問題ないと思います。

サブモジュールの移動例

以下は、既存のサブモジュールを別ディレクトリに移動する例。.gitsubmodulesもきちんと更新されますよ。

$ git submodule #移動前のサブモジュールを確認(hashは省略)
 ********** org-dir/sub-mod (***********)

$ mkdir dst-dir/ # 移動先ディレクトリの作成

$ git mv org-dir/sub-mod dst-dir/ # 移動(warningは無視)
warning: LF will be replaced by CRLF in .gitmodules.
The file will have its original line endings in your
working directory.

$ git submodule #移動後のサブモジュールの確認(hashは省略)
 ********** dst-dir/sub-mod (***********)

$ git status # 変更点の確認
On branch mv-sub-mod
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        renamed:    org-dir/sub-mod -> dst-dir/sub-mod
        
$ git diff --staged
diff --git a/.gitmodules b/.gitmodules
index *******..******* *******
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
 [submodule "sub-mod"]
-   path = org-dir/sub-mod
+   path = dst-dir/sub-mod
    url = ssh://git@xxx.xxx.xxx.xxx/yyy/zzz.git
    branch = mv-sub-mod
(略)

サブモジュールの名前変更

サブモジュールのディレクトリ名を変えるのも同じく可能です。

$ git submodule
 ********** org-dir/sub-mod (***********)

$ mkdir dst-dir/

$ git mv org-dir/sub-mod dst-dir/submod2
()
$ git status # 変更点の確認
On branch mv-sub-mod
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .gitmodules
        renamed:    org-dir/sub-mod -> dst-dir/submod2

ということで

とりあえず最近のGitでサブモジュールの場所を移動したり名前を変えたりするには、git mv で充分ですって話でした。.gitsubmodulesもちゃんと更新してくれます。

StackEdit5で使えなくなった目次生成機能の代替手段

StackEdit5では目次を自動的に作ってくれる[TOC]マーカーが使えなくなっていますが、なんとか代替手段はあるようです。

[TOC]マーカー削除の理由は?

GitHubのIssueに書かれてました、、、

他に良いサービスがあるので[TOC]マーカーの機能は陳腐化しちゃったんだよね・・・他のを使ってください・・・

ということらしいです。なら仕方がない。代替手段を試してみる。

編集中に目次を利用する方法

StackEdit5の右上にある[#]メニュー内に「Table of contents」という項目があり、これを選ぶと目次が表示されます。

GitHubのIssue#1248 で紹介されています。

Publish時に[TOC]マーカーを目次に変換する方法

また、StackEditコミュニティのHowToに、[TOC]マーカーを目次に置き換える方法が紹介されています。

HTMLへの発行(Publish)時のテンプレートで変換するようなので「プレビュー画面でリアルタイムに」は無理なのでしょう(まだ試していません)。

まとめ

便利な機能だったし削除は残念ですが、一応代替手段が提供されているので穏便に(笑)。

発行時の目次生成は試していませんが、またそのうち。

ワタシはStackEditを、主にBloggerへの投稿のために使用しているのですが、そもそも「目次が必要なほど長いのは書かない」ほうが良いのかも?とも思いはじめました。

参考リンク

  1. [TOC] in the document no longer works - Issue #1248 - benweet/stackedit
  2. Replace [TOC] markers with tables of contents in your exports - How To - StackEdit community

AWS API Gatewayから SVG(content-type: image/svg+xml)を返却する

WEBクライアントのJavaScriptからAWS API GatewayのGETメソッドを呼び出して、SVGファイルを取得する。

Lambdaを定義

以下のLambda「svgImageGet」を定義する。パラメータに応じたSVGファイルをS3からダウンロードしてAPI Gatewayへ返す関数。

const AWS = require("aws-sdk");
const S3 = new AWS.S3();
exports.handler = async (event) => {
    let responseBody = { svg: null };
    let params = {
        Bucket: "myBucket",
        Key: `svg/${event.bodyName}.svg`,
    };
    let s3obj = await (new Promise((resolve, reject) => {
        S3.getObject(params, (err, data) => {
            if(err) {
             reject(err);
            } else {
          resolve(data);
         }
        });
    }));
    responseBody.svg = s3obj.Body.toString("utf-8");
    return responseBody;
};

API Gateway の設定

前提: 任意のAPIにCORSが有効化されたメソッド:/svg-file GETが定義されている状態。

以降このGETメソッドに対する設定。

「メソッドリクエスト」の設定

「メソッドリクエスト」の「URL クエリ文字列パラメータ」にパラメータbodyNameを追加。

「統合リクエスト」の設定

「統合リクエスト」で上記Lambdaに紐づけて、content-typeapplication/jsonの「本文マッピングテンプレート」を追加。内容は以下。

{
    "bodyName" : "$input.params('bodyName')"
}

上記$input.params('bodyName')が「メソッドリクエスト」の「URL クエリ文字列パラメータ」を指しており、このJSON全体が、Lambdaの第一引数に渡される。

「統合レスポンス」の設定

「統合レスポンス」の「本文マッピングテンプレート」で、「リクエスト本文のパススルー」から「テンプレートが定義されていない場合 (推奨)」を選んでおき(デフォルト)content-typeimage/svg+xml
のマッピングテンプレートを追加する。テンプレートの内容は以下とする。

#set($inputRoot = $input.path('$'))
$inputRoot.svg

テンプレートの $inputRoot.svg が、LambdaのresponseBody.svgに対応している。

「メソッドレスポンス」の設定

HTTPのステータス 200 を開き、「200のレスポンス本文」のContent-Typeをimage/svg+xmlとする。モデルは空(Empty)で良い。

API Gatewayの呼び出し

これまで設定してきたAPIを任意のステージへデプロイした上で、JavaScriptのSDKをダウンロードする。

このSDKはHTMLのSCRIPTタグで読み込んでおき、以下の関数を実行すれば、SVGドキュメントを取得できる。

/* globals Promise, apigClientFactory */
"use strict";

let apigClient = apigClientFactory.newClient({
    apiKey: "apiKey",
});

/**
 * @async
 * @param {string} bodyName ファイルボディ名
 * @returns {Promise<xmldoc>} SVGドキュメントを返す。
 */
async function svgFileGet(bodyName) {
    let response = await apigClient.svgFileGet(
        { bodyName: bodyName }, {}, {});
    if(response == null) {
        throw new Error("Null response");
    } else if(!("status" in response)) {
        throw new Error("No status in response");
    } else if(response.status !== 200) {
        throw new Error("Request fail");
    } else if(!("data" in response)) {
        throw new Error("No data in response");
    }
    let parser = new DOMParser();
    return parser.parseFromString(
      response.data, "image/svg+xml");
};

JavaScript(ES2015)で変数を文字列リテラルに展開するとか、ヒアドキュメント的なもの

JavaScriptで文字列内に変数など(実際には式)を展開したいとき、ES2015(ES6)では、文字列をバッククォート(`` - バックティックともいうらしい)で囲む、テンプレートリテラルという記法が使用できます。

文字列中に式を展開できる

テンプレートリテラルには、${式}という形式のプレースホルダを含ませられ、評価時に式が展開されるというもの。

let value = "Template Literal";
console.log(`Hello! ${value}`);

なんだかPerlやPHPみたいですね。

複数行文字列をそのまま書ける

さらに、テンプレートリテラルの中には改行文字を含められるそうで、ヒアドキュメントみたいなことが可能。

let world = `Template
String`;

console.log(
`Hello!
${world}`
);

簡素で良いですね。

タグ付けされたテンプレートリテラルとは?

タグ付けされたテンプレートリテラルとは、テンプレートリテラルの展開内容を処理する機能(と記法?)です。

テンプレートリテラルの評価の途中で展開内容を独自の関数で受け取れて、その展開結果をオーバーライドできます。

MDNによくわからないサンプルコードが掲載されていますので、以降で説明。

var a = 5;
var b = 10;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " world"
  console.log(values[0]);  // 15
  console.log(values[1]);  // 50

  return "Bazinga!";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Bazinga!"

最初に関数tagが定義されています。

function tag(strings, ...values) {
・
・
・

tagの第一引数はstrings。第2引数のvalueは、呼び出し時に指定された第2引数から最後までを配列として受けとります。

tag`Hello ${ a + b } world ${ a * b}`;
// "Bazinga!"

最終行でtagを呼び出しており、これがタグ付けされたテンプレートリテラルという記法のようです。

コメント部分であたかも"Bazinga!"とコンソールに表示されそうに書かれていますが、出ません。あくまでも テンプレートリテラルの評価値が"Bazinga!"です。

この呼び出しでは、事前にテンプレートリテラル内のプレースホルダとそれ以外の部分に分割され、

  1. Hello - 固定文字列
  2. ${ a + b } - プレースホルダ
  3. world - 固定文字列
  4. ${ a * b} - プレースホルダ

プレースホルダ以外のHello, worldが一つの配列にまとめられて第一引数に与えられ、プレースホルダ(${ a + b }, ${ a * b})の展開結果が第2引数以降へ与えられます。

通常の関数呼び出しで表現すれば以下のようになります。

tag([`Hello `, ` world `],
	`${ a + b }`, `${ a * b}`);

互換性は?

ES2015/ES6での標準仕様。
Node.jsではv6.0以降で使えるから、今やほとんどの環境で互換性を気にする必要はなさそう。

正式名称?

一般的には「テンプレートリテラル」と呼ばれているようだけど、MDNでは「テンプレート文字列」となっている。(URLも「template-string」)。

自分をアップデートする必要性

これ、以前どこかで見かけて、なんとなく知っていたのだけど、習慣的に+でつなげたり、["a","b"].join("")などとしていました。不便だなあとは思っていましたが、どこかで「そういうものだ」とあきらめてました。

なんでもどんどん便利になっていくのが普通なのだから、定期的に知識や技術をアップデートする必要がありますね。

Bloggerのトップレベルドメインが変わってアクセス解析できていなかったという恨みごと

もともとBloggerではトップレベルドメインが、comからjpへリダイレクトされていたようですが、それがされなくなって、逆にjpからcomへ転送されるようになっていました。

おかげで、jpで登録していた Google Analytics や Google Search Console に、データが上がらなくなっていたって話です。

このブログ、ここ半年ぐらい放置していて、先日からボチボチ更新してるのですが、相変わらず アクセスほぼ0。まあそんなものだろうと半ば不貞腐れてはいたのだが、、、

しかしBloggerのダッシュボードで確認できるアクセス数はもうちょっと多い。しかしロボットを数えているにしては数が少ない気がした。

さらに Google Search Console で「なんかおかしい」的なことが表示されていて、さすがにやっぱり何かおかしいのだろうと本腰入れての調査の結果、、、

「なんだ登録しているURLが違ってるやん責任者出てこい」ということに(責任者は自分だろうか?)。

いつからこうなったのか不明です。

API Gatewayのレスポンスボディを非同期Lambdaから返却する

AWS Lambdaで Node.js v8.10(LTS)が使用できるようになっており、Lambdaを新規作成すると、exports.handler が async関数になっています。

API Gatewayからこの最新のAsync な Lambda を呼んでいる場合、returnでobjectを返してやれば、HTTPレスポンスボディとなるようですね。

Node.js v8.10 でのAPI Gateway Lambda スケルトン:

exports.handler = async (event) => {
    var response = {
     param: event, err: null, data: null, };
    try {
        response.data = await foo(event);
    } catch (err) {
        response.err = err;
    }
    return response;//これがレスポンスボディ
};

v6.10の時は、ハンドラーに渡されるcallbackを呼び出していたのですが、もしかしてPromiseを解決した値がレスポンスボディになっていたのか?今更だけど。

exports.handler = (event, context, callback) => {
    var response = {
     param: event, err: null, data: null, };
    foo(event).then( data => {
     response.data = data;
        callback(null, response);
    }).catch( err => {
     response.err = err;
        callback(null, response);
    });
};

AWS S3のバケットからテキストファイルを取り出そうとしてハマった件

AWSのS3の getObject APIで取り出したファイルの内容は、
Bufferクラスのインスタンスとして response.Bodyに収められているので、テキストファイルの中身を文字列として得たいなら、以下のようにすれば一発です。
let text = response.Body.toString("utf-8");
API叩くところも含めると以下のようになる。
"use strict";
const AWS = require("aws-sdk");
const S3 = new AWS.S3();
function S3_getObject(bucket, key) {
    return new Promise( (resolve, reject) => {
        S3.getObject(
            {Bucket: bucket, Key: key },
            (err, response) => {
                if(err) { reject(err); }
                else { resolve(response); }
            }
        );
    });
}
(async ()=> {
    let response = await S3_getObject("mybucket", "foo.json");
    let text = response.Body.toString("utf-8");
    console.log(text);
})();
しかし初めてのS3だったので、変換前に response.BodyをJSON.stringifyで確認すると、{"type":"Buffer", "data":[ ... ]} などと表示され、「なるほど data に生の配列として入ってるのね」と思ってしまい、Buffer.from(response.Body.data) とやってエラーになった。「コンストラクタの第一引数は文字列じゃないと・・・」みたいなエラー。「わけわからん」と response.Body.data を確認すると、なぜかこれが undefinedになっている(上ではJson的にキーがあると表示されているのに)。
いまだに、どうしてこうなるのか理解できないままでいる。
まあ、本質的には「APIの仕様をきちんと把握してから挑みましょう」ということなのだが。
以下、確認コードも含めて再掲(前半部分は端折ってある):
(async ()=> {
    let response = await S3_getObject("mybucket", "foo.json");
    console.log(JSON.stringify(response.Body));
        // ↑ {"type":"Buffer", "data":[ ... ]}
        // などと表示される
    console.log(JSON.stringify(response.Body.type));
        // ↑ undefined なんで?
    console.log(JSON.stringify(response.Body.data));
        // ↑ undefined なんで?
    let textFail = Buffer.from(response.Body.data);
        // ↑ Error undefindから構築できない。 
    let text = response.Body.toString("utf-8");
        // ↑ これが正解
    console.log(JSON.strngify(JSON.parse(text)));
})();

DynamoDBのセット型 BS/SS/NS について

DynamoDBのデータ型のうち「セット型」と呼ばれる特殊な型、 SSNSBS について書いています。

どれも特定の型のデータを複数要素保持できる型。
型名の1文字目が要素の型を表していて、2文字目は “Set” ですね。

DynamoDBのセット型:

  • SS - String Set : 文字列セット (*1)
  • NS - Number Set : 数値セット
  • BS - Binary Set : バイナリセット

(*1) AWS公式日本語ドキュメントでは文字セットとなっていますが、誤解しそうなので「文字列セット」としておきます。

セット型は以下のような特徴をもつ特殊な型です。

  • 複数の要素を保持できる。
  • 要素の型を限定する。
  • 要素の順序は保証されない。
  • 要素の重複は許されない

DynamoDBのAPIのパラメータやレスポンスでは、これらの型のデータは、配列として記述されます。

そのため、私は単に型限定された配列だと思いこんでいたのですが、間違いでした。ユニットテストを書いて気が付いた。テストは偉大。