バイクとプログラミング

タグ: EcmaScript

JavaScript FetchAPIでのPOSTはContent-Typeを指定しない方が良い

TLDR: JSON以外はContent-Type不要。

以前作業したコードで、呼び出されたらAjaxを用いて通信し、レスポンスに対応した適当な処理を挟んでから呼び出し元にデータを渡す、いわゆるラッパクラスのようなものが存在する。

今まで、これらの処理はjQuery.ajax()を利用してきたが、今時jQueryというのもよくないのでFetchAPIへ移行する運びとなった。

新しいコードへの移行はかなり簡単に終了することができたが、FetchAPIのContent-Type指定で多少つまずきがあったのでメモ。

FetchAPIでは、POSTを行う際、bodyに指定されたデータの型が主要なものであればContent-Typeもそれに合わせて自動的に指定される。例えばStringであればtext/plain;charset=UTF-8, URLSearchParamsであればapplication/x-www-form-urlencoded;charset=UTF-8といった具合である。これは仕様に記載されている動作である。

Fetch Standard – https://fetch.spec.whatwg.org/#body-mixin

また、Content-Typeを手動で指定した場合、そちらが優先される。例えばJSON.Stringify()の返り値はStringであるが、Content-Typeを指定すれば勝手にtext/plainにされることはなくなる。

この仕様のため、JSONを除けば大概の送信手段はContent-Typeを指定する必要がない。指定してしまうと、データの型と相違があった場合にサーバがリクエストボディをうまく解釈できない場合がある。

特にFormDataの場合、手動でヘッダを指定するとboundaryを無視することになる。そのためデータがサーバに届いたとしても、それを解釈することができない。これのため一時間ほどハマった。

余談として、当時どうしてもうまく送信できなかったのでFetchのPolyfill実装を読んでみたが、BlobSearchParamsに関する記述はあるのにFormDataに関する記述が無く困った。XHRでFormDataを判別してContent-Typeに指定しているのか?そのうち調べてみたい。

fetch/fetch.js at master · github/fetch – https://github.com/github/fetch/blob/master/fetch.js#L233

以上です。

ES Modulesではシビアな循環参照

タイトルの通りです。ES Modulesでどハマりしました。

DOMを所定の形式に変換して出力を行う処理を書いていましたが、その中で循環参照が生まれ二進も三進もいかなくなってしまったためメモを残します。

下記のようなファイル群を用意します。Chrome Version 74.0.3729.169で再現。

// script.js
import ModuleA from './ModuleA.js';

new ModuleA().createModuleB();
// ModuleA.js
import ModuleB from './ModuleB.js';

export default class ModuleA {
    createModuleB() {
        return new ModuleB();
    }
}
// ModuleB.js
import ModuleA from './ModuleA.js';

export default class ModuleB extends ModuleA {
}

上記のコードをHTMLから読み込むと下記のようなエラーが出ます。

ReferenceError: can't access lexical declaration `ModuleA' before initialization

まさか循環参照が悪さをしているとは知らず、直感的にはわかりにくいエラーで大変混乱しました。これは普段宣言していない変数を参照しようとすると出るエラーです。

これを解決するには、ModuleA.jsを読み込む前に、あらかじめModuleB.jsを読み込んでおく必要があります。

// script.js
import ModuleB from './ModuleB.js'; //  ここで読み込む
import ModuleA from './ModuleA.js';

new ModuleA().createModuleB();

これで問題なく読み込むことができました。

まだ理解が浅く、なぜこうなるのかを断言できないので詳しくは次回以降にメモできればいいかなと思います。

Powered by WordPress & Theme by Anders Norén