JavaScript入門の書籍を読んで書いてあったこと+自分で調べたことのメモ書きその7。非同期処理、モジュール(export、import)。
非同期処理
fetchとPromiseオブジェクト
fetch
fetch(resource[, options])
はネットワークから指定したリソースを取得する関数である。
引数はURL、Requestオブジェクト。
options でリクエストメソッドの指定などができる。
リソースが取得できたらPromiseオブジェクトを返す。
Promise.protype.then
Promise のメソッド then(onFulfilled[, onRejected])
は最大2つの引数を取る。1番目の引数は処理が成功した(Promiseが履行された場合)のコールバック関数、2番目の引数は処理が失敗した場合(Promiseが拒否された)場合のコールバック関数である。
戻り値はPromiseで、then()メソッドのチェーンにより連続した非同期処理を記述することができる。
let result = document.querySelector("#result");
let url = "/test.json";
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("failed.");
}
return response.json();
})
.then((data) => {
result.innerText = data.text;
});
前のthen の引数である無名関数の戻り値が次のthenの引数になる。
4行目のthenの引数 response はReponseオブジェクト、10行目のthenの引数dataはJSONのオブジェクトである。
response.json()
の戻り値はPromiseオブジェクトである。
fetch が成功したかのチェックは5行目のように response.ok
で行う。404の場合はPromiseは履行されるので。
async/await
async/awaitを使うことによってfetchとPromiseのthenを使った記述よりもさらに通常の処理に近い形で非同期処理を記述できる。
async/awaitでは非同期処理を行いたい処理を非同期化数の中にまとめて書く。関数定義の前にasync
を付けると非同期関数となる。非同期関数内の処理でawait
のところでPromiseが決定(履行または拒否される)まで一時停止する。
先のfetchとPromiseのthenを使った例をasync/awaitを使って書き換えると下記になる。3行目にawaitいるかな?とも思ってしまうがreponse.json()
(Responseのインスタンスメソッド)の戻り値はPromiseオブジェクトなので必要なのだろう。ようはPromiseオブジェクトを返すところにawaitがいると考えればいいのではないかと思う。たぶん。
async function getTestJSON(url) {
let response = await fetch(url);
let data = await response.json();
let result = document.getElementById("result");
result.innerText = data.text;
}
getTestJSON("/test.json");
モジュール
HTMLにscriptタグを複数書けば複数のJavaScriptファイルを読み込むことができる。しかしその場合は同名の変数、関数があると名前が衝突する。
この問題はモジュールで解決できる。モジュールはスコープが独立しておりモジュール間では関数などの定義の段階では名前の衝突が起こらない。
。
モジュールの使用にはimportとexportを利用する。HTMLではscriptタグのtype属性をmoduleとする。
※importとexportでモジュールを使う方式は ES Modules
。これとは別に CommonJS
というモジュールを使う仕組みがある。よく名前を聞く node.js
は CommonJS を使っている。
export
export { 名前1 [as 別名1, 名前2... }
波括弧の中にカンマ区切りでエクスポートするモジュールの定数名、関数名、クラス名を書く。as
で別名を付けてエクスポートすることもできる。
下記のように宣言と同時にエクスポートすることもできる。
export const c1 = "abc";
export function someFunc() {
console.log("someFunc");
}
デフォルトエクスポートというものがあるが、そちらはとりあえず詳細は触れずにおく(正直よくわからない)。
import
import { 名前1 [as 別名1, 名前2... } from モジュールのファイルのパス
import * from モジュールのファイルのパス
波括弧の中にカンマ区切りでインポートする定数名、関数名、クラス名を書く。as
で別名を付けてインポートすることもできる。波括弧の部分を*
にすると指定したモジュールでエクスポートされているものすべてをインポートする。
import * from as モジュールオブジェクト名 from モジュールのファイルのパス
as
でモジュールオブジェクトを作成すると、エクスポートしたものを モジュールオブジェクト名.エクスポートしたものの名前
で利用できる。
例:
module_a.js:testFunc
とmaskObject
をエクスポートしている。maskObject
にはsomeObject
という別名を付けている。
const testFunc = () => {
console.log("module_a testFunc!");
};
const maskObject = {
show() {
console.log("module_a maskObject!");
},
};
const innerFunc = () => {
console.log("innerFunc!");
}
export { testFunc, maskObject as someObject };
console.log("module_a export side");
module_b.js:testFunc
をエクスポートしている。modulea.js でエクスポートした関数と同じ名前。
const testFunc = () => {
console.log("module_b testFunc!");
};
export { testFunc };
console.log("module_b export side");
import.js:module_a.js
, module_b.js
でエクスポートされた関数、オブジェクトをインポートしたして利用している。module_b.js からモジュールオブジェクトmoduleB
を作成して利用している。
import { testFunc, someObject } from "/js/modules/module_a.js";
import * as moduleB from "/js/modules/module_b.js";
testFunc();
someObject.show();
moduleB.testFunc();
console.log("import side");
HTMLファイル:scriptタグで type="module"
としている。
<script src="/js/import.js" type="module"></script>
実行結果(consoleへの出力)
module_a export side
module_b export side
module_a testFunc!
module_a maskObject!
module_b testFunc!
import side
参考情報
書籍
解きながら学ぶJavaScriptつみあげトレーニングブック | マイナビブックス 9章
WEBページ
フェッチ API の使用 - Web API | MDN
Promise - JavaScript | MDN
Response.json() - Web API | MDN
JavaScript モジュール - JavaScript | MDN
CommonJSとES Modulesについてまとめる