記憶度
14問
35問
0問
0問
0問
アカウント登録して、解答結果を保存しよう
問題一覧
1
Promise.any について説明せよ Resolveとなる条件と返されるエラーも説明せよ
Promise.allがアンド文ならこれはオア文的な位置付け fulfilledが一つあれば正常(他は無視) 全部rejectedされたら異常として扱う。 →promiseの値にはAggregateErrorが設定される。 失敗した promise のエラーオブジェクトは AggregateError オブジェクトの errors プロパティでみれる! Promise.race と同様ですが、最初の履行した(fulfilled) promise のみを待ち、その結果を得ます。 指定された promise がすべて reject された場合、返却された promise は AggregateError で reject されます(errors プロパティにすべての promise エラーを格納する特別なエラーオブジェクトです)。 構文は次の通りです: let promise = Promise.any(iterable); 例えば、ここでは結果は 1 になります: Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)), new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 最初の promise が一番はやいですが、reject されたため、2つ目の promise が結果になっています。最初の履行した(fulfilled)promise が “レースに勝ち”、他の結果はすべて無視されます。 これは promise がすべて失敗した例です: Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000)) ]).catch(error => { console.log(error.constructor.name); // AggregateError console.log(error.errors[0]); // Error: Ouch! console.log(error.errors[1]); // Error: Error! }); ご覧の通り、失敗した promise のエラーオブジェクトは AggregateError オブジェクトの errors プロパティで利用可能です。
2
Promise.resolve関数について説明せよ
Promise.resolve(value) は結果 value をもつ解決された promise を作成します。 promiseが引数にきたら、そのpromiseをそのままかえす 以下と同様です: let promise = new Promise(resolve => resolve(value)); 同期的に即fulfilledとなる。 このメソッドは、関数が promise を返すことが期待される場合の互換性のために使用されます。
3
並列に複数の URL を取得したいです。 これはそのためのコードです: let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; Promise.all(urls.map(url => fetch(url))) // 各レスポンスに対し、そのステータスを表示 .then(responses => { // (*) for(let response of responses) { alert(`${response.url}: ${response.status}`); } )); 問題は、任意のリクエストが失敗した場合、Promise.all はエラーで reject し、他のすべてのリクエストの結果を失うことです。 これは良くありません。 行 (*) で配列 responses がフェッチに成功したレスポンスオブジェクトと、失敗したエラーオブジェクトを含むようにコードを修正してください。
Promise.all が動作する方法を変えることはできません: もしエラーを検出した場合、それを reject します。したがって、エラーが発生しないようにする必要があります。代わりに、 fetch エラーが発生した場合、それを “通常の” 結果として扱う必要があります。 これはその方法です: Promise.all( fetch('https://api.github.com/users/iliakan').catch(err => err), fetch('https://api.github.com/users/remy').catch(err => err), fetch('http://no-such-url').catch(err => err) ) つまり、.catch はすべての promise のエラーを取り、それを正常に返します。 catchでうけとるエラーはそのまま返せばエラーがresultとして設定される promise の動作ルールにより、.then/catch ハンドラが値(エラーオブジェクトか他のなにかなのかは関係ありません)を返した場合、実行は “正常の” フローを続けます。 したがって、.catch は、エラーを “正常の” 結果として外側の Promise.all に返します。
4
以下のコードはコールバック関数を引数にとっている。 以下をラップしてpromiseを返すようにせよ loadScriptPromiseという関数を作成せよ function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } // 使用例: // loadScript('path/script.js', (err, script) => {...})
loadScriptはコールバック関数を引数としているので、コールバック関数でreject、resolveを呼び出すようにしてやれば、executorとして扱うことができるので、 簡単にラップできる let loadScriptPromise = function(src) { return new Promise((resolve, reject) => { loadScript(src, (err, script) => { if (err) reject(err) else resolve(script); }); }) } // 使用例: // loadScriptPromise('path/script.js').then(...)
5
以下のような2つの引数 (err, result) を持つコールバックを受けつける関数について、汎用的にデコレーションを行い、promiseを返すようにすることができるヘルパー関数 promisifyを作りたい。 どうするか? function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } // 使用例: // loadScript('path/script.js', (err, script) => {...})
入力はラップしたい関数 出力はラッパー関数となる。 使用感は以下の通り変更となる。 loadScript('path/script.js', (err, script) => {...}) ↓ let loadScriptPromise = promisify(loadScript); loadScriptPromise('path/script.js').then(); デコレーターされた関数はプロミスを返す必要がある。 executorでは、ラップしたい関数をthisの実行コンテキストで呼び出したいため、 callを利用して呼び出す。 その際にパラメータとして設定するコールバック関数は、 errなら、reject そうでないならresolveするように定義した関数である。 ラップ対象の関数が完了した際、このパラメータに設定したコールバック関数が呼ばれ、promiseのステータスが変更されるようになる。 あと、ラップした関数呼び出し時に、上記の例でいうsrcを渡す考慮をいれるため 出力するラッパー関数の引数は …argsで受け取り可能にし、 callメソッドでargsと上記のコールバック関数を渡してやる必要がある。 上記に基づいて作成すると以下のようなかんじ。 実際には、複数の関数を promise 化する必要があるかもしれません。この場合はヘルパーを用意するのが便利です。 promisify(f) は Promise 化する関数 f を引数に取り、ラッパー関数を返します。 function promisify(f) { return function (...args) { // ラッパー関数を返します return new Promise((resolve, reject) => { function callback(err, result) { // f のためのカスタムコールバックを定義 if (err) { return reject(err); } else { resolve(result); } } args.push(callback); // 引数の末尾にカスタムコールバックを追加 f.call(this, ...args); // 元の関数を呼び出します }); }; }; // 使用例: let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); コードは多少複雑に見えますが、loadScript 関数の Promise 化をしている。
6
前問では、元の関数がコールバック関数を実行する際に、引数は(err,result)が期待されていた 以下のように複数の結果が期待されている場合はどうするか? err,res1,res2… errなら callback(err)で実行 正常なら callback(null,res1,res2)で実行する感じ
ラッパー対象となる元の関数では、 パラメータとして受け取ったcallback関数を以下のような感じで起動する。 成功の場合: callback(null,結果1,結果2) この場合は、結果1と結果2を含む配列でresolveする感じになる。 function callback(err, ...results) { // f のカスタムコールバック 複数のコールバックの結果の配列を返す promifify の修正です: promisify(f) が呼ばれた場合は、前問と同様に動作します。 promisify(f, true) が呼ばれた場合は、コールバックの結果配列で resolve する promise を返します。これが複数の引数をもつコールバックに対するものです。 // 結果の配列を得る場合は promisify(f, true) function promisify(f, manyArgs = false) { return function (...args) { return new Promise((resolve, reject) => { function callback(err, ...results) { // f のカスタムコールバック if (err) { return reject(err); } else { // manyArgs が指定されている場合、すべてのコールバック結果で resolve します resolve(manyArgs ? results : results[0]); } } args.push(callback); f.call(this, ...args); }); }; }; // 使用例: f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...)
7
いままでやったようなコールバックを受け付ける関数をpromiseを返す関数に変換することをなんというか?
Promisificationという。
8
いままでやったようなPromisificationについて、制限事項を記載せよ
Promisification はコールバックを1度だけ呼ぶ関数のみを対象としています。 →つまり、コールバック地獄のようなコールバックの内部でコールバックを呼ぶような処理があると、一度目でresolveかrejectしてしまうので、処理が途中でおわってしまうってこと? →コールバック関数を完全にプロミス化できない それ以降の呼び出しは、コールバック関数を設定しないので、無視されます。
9
以下のコードだが、すぐにpromiseが解決されている。 どっちのアラートが先に表示されるか? let promise = Promise.resolve(); promise.then(() => alert("promise done!")); alert("code finished");
code finishedが先に表示される! Promise の .then/.catch/.finally ハンドラ やは常に非同期で実行されるため。 たとえ Promise がすぐに解決されたとしても、.then/.catch/.finally の 下 にあるコードはこれらのハンドラの前に実行されます。 実行すると、code finished が最初に現れ、その後 promise done! が表示されます。 Promise は最初から確実に終わっているので、これは奇妙です。
10
非同期タスクは内部でどのように管理されているか?
microtask キューといったやつで管理されている。 キューは先入れ先出し(first-in-first-out)です: 先にキューに入れられたタスクが最初に実行されます。 タスクの実行は他になにも実行されていないときにだけ開始されます。 簡単に言うと、Promise (pending)が準備できると、その .then/catch/finally ハンドラはチェーン分全てキューに入れられます。 それらはまだ実行されていません。JavaScriptエンジンは、現在のコードがないときにキューからタスクを取り、実行します。 なので、前問では先に “code finished” が表示されました。 同期的にはpromiseチェーンはmicrotaskにハンドラを登録してき。 現在のコードがおわり、 promiseのステータス次第実行する感じ!
11
以下コードのような未処理の拒否(rejectをcatchしない場合のこと)は、 内部ではどのように動いているかマイクロタスクキューを用いて解説せよ。 let promise = Promise.reject(new Error("Promise Failed!")); // 実行されます: Promise Failed! window.addEventListener('unhandledrejection', event => alert(event.reason));
"未処理の拒否" は、microtask キューの先頭から最後まで Promise エラーが処理されない場合に発生します。 つまり、microtaskに入れられたキュー(.then.catch.finally)をみていって、catchが見つからなかった場合に未処理の拒否扱いとなるということ! ※現在の同期的なコードがおわってから確認している!
12
仮に、次のように後でエラーを処理すると どのようにアラートが表示されるか? let promise = Promise.reject(new Error("Promise Failed!")); setTimeout(() => promise.catch(err => alert('caught')), 1000); window.addEventListener('unhandledrejection', event => alert(event.reason));
実行すると、最初に Promise Failed! が表示され、その後 caught が表示されます。 つまりunhandledrejectionイベントが先に動いた。 なぜこうなるか? let promise = Promise.reject(new Error("Promise Failed!")); ↑この処理は同期的に行われる つまり、promiseなrejectされた状態で作成される。 その後、setTimeoutの設定とリスナー登録を実行する。 現在のコードが完了したタイミングで、microtaskキューのを確認を行う 。キューにはまだ何も登録されてない状態なので、 unhandledrejectionイベントが呼び出される。 その後、setTineoutで設定した処理が動く、 これはこれでキャッチ処理はうごく!! ちなみに以下のパターンだとAaa、caughtの順で実行される。 let promise = Promise.reject(new Error("Promise Failed!")); //ハンドラの登録を行う promise.catch(err => alert('caught')); alert(‘Aaa’); // 実行されません: window.addEventListener('unhandledrejection', event => alert(event.reason));
13
ちょっと混乱したのでメモ
promise作成処理は同期的! ただし、thenとかcatchとかfinallyは非同期で実行する!!
14
async キーワードとは何か説明せよ
関数に対して付与でき、その関数はpromiseを返すこと保証するものである。 戻り値がpromise出ない場合は、自動的にpromiseでラップしてくれる 例えば、下のコードは 結果 1 を持つ解決された promise を返します。テストしてみましょう: t it: async function f() { return 1; } f().then(alert); // 1 …明示的に promise を返すこともでき、それは同じです: async function f() { return Promise.resolve(1); } f().then(alert); // 1 したがって、async は関数が promise を返すことを保証し、非promise をその中にラップします。
15
awaitキーワードについて説明せよ
async関数の中でのみ利用することができる。 特定のプロミスの前につけることができ、 そのプロミスが確定するまで待機するというもの。 以下のように戻り値は、プロミスのresultとなる。 let result = await promise; 以下は1秒で解決する promise の例です: async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); let result = await promise; // promise が解決するまで待ちます (*) alert(result); // "done!" } f(); alert(result); // "done!"の部分がコールバック関数扱い! promiseが確定されると再開できる! await は文字通り promise が確定するまで JavaScript を待ってから、その結果で続くことに注目しましょう。その間、エンジンは他のジョブ(他のスクリプトを実行し、イベントを処理するなど)を実行することができるため、CPUリソースを必要としません。 これは、promise.then よりも promise の結果を得るためのより洗練された構文です。読みやすく、書くのが簡単です。
16
以下コードは構文エラーとなる。なぜか? function f() { let promise = Promise.resolve(1); let result = await promise; // Syntax error }
非async関数で await を使おうとした場合、構文エラーになります。: async を置き忘れた場合にこのエラーが発生します。先程言ったように、await は async function の中でのみ動作します。 awaitするなら、promiseが返却されることが保証されてないといけないらしい
17
以下コードを async awaitを使って実装せよ fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise(function(resolve, reject) { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); })) // 3秒後にトリガされます .then(githubUser => alert(`Finished showing ${githubUser.name}`));
.then 呼び出しを await に置き換える必要があります。 また、機能させるために関数でラップし async にする必要があります。 戻り値はpromiseのresultにgithubUserを登録したものである async function showAvatar() { // JSON を読み込む let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // github ユーザを読み込む let githubResponse = await fetch(`https://api.github.com/users/${user.name}`); let githubUser = await githubResponse.json(); // アバターを表示する let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); // 3秒待つ await new Promise((resolve, reject) => setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar();
18
無名のasync関数をつくれ
こんな感じ (async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })(); アロー関数でasyncするならこう!
19
awaitはトップレベルのコードで動作するか?
最近のブラウザは、モジュールの内側ではトップレベルの await は問題なく動作します。 モジュール未使用、あるいは古いブラウザでサポートが必要な場合、無名の async 関数でラップする方法があります。 次のようになります: (async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();
20
awaitはthenable を許容するか?
await は thenable を許容します promise.then のように、await は thenable オブジェクト(then メソッドを呼ぶことができるもの)を使うことができます。 繰り返しになりますが、このアイデアは、サードパーティオブジェクトは promise ではなく、promise 互換である場合があるということです .then をサポートしている場合、await で使えます! またawaitタイミングでthenが起動される。 例えば、ここで await は new Thenable(1) を許容します: class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // 1000ms 後に this.num*2 で解決する setTimeout(() => resolve(this.num * 2), 1000); // (*) } }; async function f() { // 1秒待って、結果は 2 になる let result = await new Thenable(1);//インスタンス後、awaitされたタイミングでthenメソッドが動く!! alert(result); } f(); resolve, reject をサポートしている関数をみつけてその関数を実行させるらしい。 要はexecutorを実行させる。 なのでthenableの場合はthenがexecutor となるため、 awaitは内部でthenを起動し、resolve, reject 関数をパラメータとして渡す。 そしてresolveかrejectが呼ばれるまで待つということか。 resolve, reject をサポートしている関数ということは、 thenじゃなくてもいい?
21
await promiseでエラーがかえってきたときはどのような動作となるか?
拒否(reject) の場合はエラーをスローします。それはちょうどその行に throw 文があるように振る舞います。 このコードは: async function f() { await Promise.reject(new Error("Whoops!")); } …これと同じです: async function f() { throw new Error("Whoops!"); } 未処理のrejectはエラー扱いになるが、 非同期処理なので、処理落ちはしない。 awaitすることでエラーがあっても、通常のスローされたエラーとして扱うことができる
22
await promiseでrejectの場合はエラーを投げるということで何が利用できるようになるか?
try catch文が利用できる。 エラーは try..catch でキャッチすることができ、それは通常の throw と同じ方法です: async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { alert(err); // TypeError: failed to fetch } } f(); エラーの場合、コントロールは catch ブロックにジャンプします。
23
await promiseでrejectされた場合に try catchでエラーをキャッチせずにべつの方法でキャッチする方法がある。なにか?
もし try..catch がない場合、async 関数 f() の呼び出しによって生成された promise は拒否されます。それを処理にするには .catch を追加します。: async function f() { let response = await fetch('http://no-such-url'); } // f() は拒否された promise になる f().catch(alert); // TypeError: failed to fetch // (*) ちなみに .catch を追加し忘れると、未処理の promise エラーを得ます
24
以下のコードは何をしているか説明せよ // 結果の配列をまつ let results = await Promise.all([ fetch(url1), fetch(url2), ... ]);
Promise.allはただ、引数のイテラブルのpromiseを並列として、一つのpromiseとして管理(全部終わるまで待機状態)するだけ。 awaitをつけることで、待機状態がおわるまで待つことができる。
25
“通常” の関数があります。そこから async 呼び出しを行い、その結果を使うにはどうすればよいでしょう? awaitは使わない方法で! async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // ...ここに何を書きますか? // async wait() をして 10 を取得するのを待ちます // 覚えておいてください、"await" は使えません }
awaitは使えないので、promiseとして利用し.thenにおきかえる wait()←これでpromiseオブジェクトが返却されるので、thenでコールバックを登録すればいい async function wait() { await new Promise(resolve => setTimeout(resolve, 1000)); return 10; } function f() { // 1秒後に 10を表示 wait().then(result => alert(result)); } f();
26
Promiseについてのまとめ
promise生成処理は同期的 promiseに対するハンドラは非同期で起動する Promiseはexectorの状態を管理するオブジェクトで、インスタンスが作成された時点で起動する →同期的に即resolveされれば、resolveされたpromiseができる。 これは同期的。 Promise.allなんかも同様で複数のpromiseを一つにまとめて状態管理するってだけ。 後続処理をかきたいなら、必ずthenをつけないとだめ。 そしてthenのハンドラは絶対に非同期実行になる! 未処理のrejectは、 現在のコードが終了してから、マイクロタスクキューを確認して、未処理であることを判断し、エラー扱いとする。 処理落ちはせず、 そして、unhandledrejectionイベントを起動する。 await promiseはその場でエラー出た扱いしてくれる。
27
ジェネレータとはなにか?
通常の関数は、単一の値だけを返します(もしくはなにも返しません)。 ジェネレータオブジェクトは、要求に応じて次々に複数の値、場合によっては無限の数の値を返す(“生み出す”)ことができる。
28
ジェネレータを作成するにはどうするか?、
function*、いわゆる “ジェネレータ関数” を使用する必要があります。 このようになります: function* generateSequence() { yield 1; yield 2; return 3; } generateSequence() が呼ばれたとき、コードは実行されません。代わりに、“ジェネレータ” と呼ばれる特別なオブジェクトを返します。 // "ジェネレータ関数" は "ジェネレータオブジェクト" を生成します。 let generator = generateSequence(); 理解すべき最も重要なことは、ジェネレータ関数は通常の関数とは異なり、コードを実行しないことです。それらは “ジェネレータ工場(ファクトリー)” として機能します。 function* の実行はジェネレータを返し、その後、ジェネレータに値を要求します。
29
以下コードを参考にジェネレータオブジェクトの動作を説明せよ function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); let one = generator.next(); alert(JSON.stringify(one)); また、どのようなオブジェクトがかえるか?
generator オブジェクトは “凍結された関数呼び出し” と捉えることができます。: オブジェクト作成時に、コードの実行は最初の部分で一時停止されます ジェネレータのメインのメソッドは next() です。呼ばれると、最も近い yield <value> 文まで実行を再開します。その後、実行は一時停止し、値は外部のコードに返却されます。 nextでreturn文に到着すると、 それが最終結果となる。 以下のようになる。 let three = generator.next(); alert(JSON.stringify(three)); // {value: 3, done: true} *のある関数はジェネレーター関数 ジェネレーター関数を利用してできるのがジェネレーターオブジェクト。
30
ジェネレータのnextメソッドの戻り値はなにか?
next() の結果は常にオブジェクトです: value: 戻された値 done: コードがまだ終わっていない場合は false, そうでなければ true. nextをもつということは ジェネレーターはイテレーターオブジェクトである。
31
ジェネレータ.next()で最後まで処理し、 再度nextを読んだらどうなるか? ジェネレータは巻き戻すこと(ロールバック)できるか?
最後でよんだとにと同じオブジェクトがかえる ジェネレータを “ロールバック” する方法はありません。しかし、generateSequence() 呼び出しによって、別の物を作ることはできます。
32
function* f(…) それとも function *f(…)?
しかし、アスタリスク *はジェネレータ関数であることを表し、名前ではなく種類を表すので、通常は最初の構文がより好まれます。したがって、function キーワードに付けてください。 function* f(…)
33
ジェネレータの特徴として、⚪︎⚪︎ラブルもいえるか?
イテラブルである。 ジェネレータは 反復可能(iterable)です。 for..of によって、値をループすることができます: イテラブルとは、symbol.iteratorに定義したメソッドにイテレーターオブジェクトを返すことだったけども、 ジェネレータの場合も同様にサポートしていることになる。 また、ジェネレーター自体はnext をサポートするのでイテレーターオブジェクトともいえる。
34
以下コードは何を出力するか? function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence(); for(let value of generator) { alert(value); }
1、2 まで出力する。 3は出力しない!! これは、done: true のとき、for-of イテレーションは最後の value を無視するからです。 なので、すべての結果を for..of で表示したい場合は、それらを yield で返さなければなりません: function* generateSequence() { yield 1; yield 2; yield 3;//doneはまだfalse 返却するものがないのでreturnは省略 最後までいくとdone:trueになる } let generator = generateSequence(); for(let value of generator) { alert(value); // 1, 次に 2, 次に 3 }
35
ジェネレータを配列に変換するにはどうするか?
当然、ジェネレータは反復可能なので、スプレッド演算子... のような、関連するすべての機能を呼び出すことができます。: function* generateSequence() { yield 1; yield 2; yield 3; } let sequence = [0, ...generateSequence()]; alert(sequence); // 0, 1, 2, 3 上のコードでは、...generateSequence() は iterable をアイテムの配列に変換します
36
以下はfor..of などで呼ばれた際に、 カスタムイテレーターオブジェクトを返却するコードである。 これを変更し、イテレーターではなくジェネレータを返却するようにせよ let range = { from: 1, to: 5, // for..of は最初にこのメソッドを一度呼び出します [Symbol.iterator]() { // ...これは iterator オブジェクトを返します: // 以降, for..of はそのオブジェクトでのみ機能し、次の値を要求します。 return { current: this.from, last: this.to, // next() は for..of ループの各イテレーションで呼ばれます next() { // 値をオブジェクトとして返す必要があります {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; alert([...range]); // 1,2,3,4,5
ジェネレータを Symbol.iterator として提供することができます let range = { from: 1, to: 5, *[Symbol.iterator]() { // [Symbol.iterator]: function*() の短縮記法 for(let value = this.from; value <= this.to; value++) { yield value; } } }; alert( [...range] ); // 1,2,3,4,5 range オブジェクトはいま反復可能です。 これは非常に上手く機能します。なぜなら、range[Symbol.iterator]() が呼ばれたとき、: オブジェクトを返します(いまはジェネレータです)。 そのオブジェクトは .next() メソッドを持っています(そう、ジェネレータはそれを持っています)。 これは、{value: ..., done: true/false} の形式で値を返します。 もちろん、これは偶然ではありません。ジェネレータは iterable をより簡単にすることを目的としているので、そのようにすることができます。
37
永遠に続くジェネレータは作成可能か?
できる。 上の例では、有限の数字列を生成しましたが、永遠に値を生成するジェネレータを作ることも可能です。例えば、終わりのない疑似乱数の列です。 yield 乱数を無限ループ的な感じ for..of の中で使用する場合は、 break を必要とします。そうでなければ、ループは永遠に繰り返され、ハングします。
38
以下のコードが何をしているか解説せよ function* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; } function* generatePasswordCodes() { // 0..9 yield* generateSequence(48, 57); // A..Z yield* generateSequence(65, 90); // a..z yield* generateSequence(97, 122); } let str = ''; for(let code of generatePasswordCodes()) { str += String.fromCharCode(code); } alert(str); // 0..9A..Za..z
0から9,AからZ,aからzの文字コードを出力していくジェネレータである。 yield* という見慣れない構文があるが、 これは別のジェネレータに対して 実行を委譲することであり処理を合成する機能のことである。 構文はこう yield* ジェネレーター 普通にジェネレーターを作成してしまうだけだと.nextメソッドが呼ばれない限りなにもしないので、デリゲートするために上記の構文を用意されてそう つまり、以下でいうと、 generateSequence(48, 57);の機能がここに合成される。つまり、この関数内でベタガキされたものとして扱う。 function* generatePasswordCodes() { // 0..9 yield* generateSequence(48, 57); この例にある特別な yield* ディレクティブは合成を担当します。これは、別のジェネレータに実行を 委譲(デリゲート) します。あるいは、単純に言うと、ジェネレータを実行し、あたかもそれらが呼び出し元のジェネレータ自体によって行われたかのように、それらの yield を外部に透過的に転送します。 結果は、入れ子のジェネレータのコードがインライン展開された場合と同じです。: function* generateAlphaNum() { // yield* generateSequence(48, 57); for (let i = 48; i <= 57; i++) yield i; // yield* generateSequence(65, 90); for (let i = 65; i <= 90; i++) yield i; // yield* generateSequence(97, 122); for (let i = 97; i <= 122; i++) yield i; }
39
いままでせつめいしたジェネレータの機能での他になにができるか?説明せよ
yield は双方向に値を渡すことができる。: 結果を外部に返すだけでなく、ジェネレータ内部に値を渡す事もできます。 let result = yield "2 + 2?"; // (*)なら yield "2 + 2?";で値を外部に渡す generator.next(4) で値4を内部(ジェネレータ側)に渡す ジェネレータ内部では let result = yield "2 + 2?"; ↑のとこで受け取る。 そうするためには、引数を持つ generator.next(arg) を呼び出す必要があります。この引数は yield の結果になります。 つまり、argに指定した値がyield側に設定されるということ。 例を見てみましょう: function* gen() { // 質問を外側のコードに渡して答えを待ちます let result = yield "2 + 2?"; // (*) alert(result); } let generator = gen(); let question = generator.next().value; // <-- yield は値を返します generator.next(4); // --> 結果をジェネレータに渡します 解説するとまず、yield "2 + 2?"; // (*)まで処理が行われ、ジェネレータ外部では "2 + 2?"を取得できる。 そのごgenerator.next(4);を呼ぶことによって 4の値を “今”のyieldに渡して、処理を進めることができる。 なので、result に4が格納され、アラートされる。
40
以下のコードを解説せよ function* gen() { let ask1 = yield "2 + 2?"; alert(ask1); // 4 let ask2 = yield "3 * 3?" alert(ask2); // 9 } let generator = gen(); alert( generator.next().value ); // "2 + 2?" alert( generator.next(4).value ); // "3 * 3?" alert( generator.next(9).done ); // true
これもyield の双方向の例 関数のコードと直接やりとりができるので不思議な感じ。 まず、.next() は実行を開始します。そして最初の yield に到達します。 結果は外部のコードに返却されます。 2つ目の .next(4) は、4 を最初の yield の結果としてジェネレータに戻し、実行を再開します。 …2つ目の yield に到達します。これはジェネレータ呼び出しの結果になります。 3つ目の .next(9) 2つ目の yield の結果として 9 をジェネレータに渡し実行を再開します。 そして関数の終わりに到達するので、done: true です。 これは “ピンポン” ゲームのようです。各 next(value) (最初のものを除く) はジェネレータに値を渡し、それは現在の yield の結果になり、次の yield の結果を戻します。
41
yieldをとおしてジェネレータ関数と外部のコードでやりとりしているとき、 外部からジェネレータ内部へエラーをスローするにはどうするか? またジェネレータ内部で処理されなかったエラーはどうなるか?
エラーを yield に渡すには、generator.throw(err) を呼び出す必要があります。この場合、err は yield のある行に投げられます。 例えば、ここで "2 + 2?" の yield はエラーになります: function* gen() { try { let result = yield "2 + 2?"; // (1) alert("The execution does not reach here, because the exception is thrown above"); } catch(e) { alert(e); // 投げられたエラーを表示します } } let generator = gen(); let question = generator.next().value; generator.throw(new Error("The answer is not found in my database")); // (2) 外側からジェネレータ内部へエラー送信 エラーは、行 (2) でジェネレータにスローされ、yield のある行 (1) で例外となります。上の例では、try..catch がそれをキャッチし表示しています。 ジェネレータでエラーをキャッチしない場合は以下の通り!! 他の例外のように、ジェネレータは呼び出しコードで “落ちます”。 呼び出しコードの現在の行は、(2) とラベル付けされた generator.throw を持つ行です。なので、次のようにここでキャッチすることができます: function* generate() { let result = yield "2 + 2?"; // この行でエラー } let generator = generate(); let question = generator.next().value; try { generator.throw(new Error("The answer is not found in my database")); } catch(e) { alert(e); // エラーを表示します } ここでエラーをキャッチしなければ、通常どおり、外部の呼び出しコード(あれば)へ渡され、キャッチされなければスクリプトが強制終了します。
42
テストなどで乱数が必要な場合 JavaScript では Math.random() を使うことができます。 しかし、何か問題が起きた場合、まったく同じデータを使用して繰り返しテストができればよいです。 そのため、どのような機能がジェネレータで実装できるか?、
そのために、“シード疑似乱数ジェネレータ” と呼ばれるものが使われます。 それらは最初の値である “シード(種)” を取り、以降公式を使って次の値を生成します。 つまり、シード1に対して、こんだけの乱数のパターンが生成できて、 次回テスト時も、シード1を指定すれば同じパターンの乱数となるので、テストがらく! これは、このような公式の例で、幾分か一様に分布した値を生成します。: next = previous * 16807 % 2147483647 シードに 1 を使うと、値は次のようになります: 16807 282475249 1622650073 …など… つまり、入力はseedでそこから上記のような乱数を作成するジェネレータをつくる。 function* pseudoRandom(seed) { let value = seed; while(true) { value = value * 16807 % 2147483647 yield value; } }; let generator = pseudoRandom(1); alert(generator.next().value); // 16807 alert(generator.next().value); // 282475249 alert(generator.next().value); // 1622650073
43
非同期のイテレーションとはなにか?
非同期処理をfor文などで、繰り返し実行可能とする操作のこと
44
以下はイテラブルなカスタムオブジェクトである。 出力結果としては、 1から5までをアラートで表示するというものである。 これを非同期イテラブルなオブジェクトとして扱い、出力結果を1から5まで毎回一秒間待機してからアラートで表示するというふうにしたい。 どうやるか? let range = { from: 1, to: 5, [Symbol.iterator]() { // for..of の最初に一度呼ばれます return { current: this.from, last: this.to, next() { // 各イテレーションで呼ばれ、次の値を取得します if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; for(let value of range) { alert(value); // 1 then 2, then 3, then 4, then 5 }
nextメソッドで非同期処理setTimeout を呼び、一秒間タイマー後に returnすることで、 イテレーションするときに 毎回遅延を設けることができるのではないか? ↑一秒間タイマー後にreturnというのがコールバック関数でやりたいこととなるが、 コールバック関数内でのreturn処理はできないので、コールバック関数の結果を取得するには、promiseで取得する必要がある。 通常のforではpromiseが完了することをまたないため、結果のないpromiseを処理してしまうこととなる。 オブジェクトを非同期的に反復可能とするには、: Symbol.iterator の代わりに、Symbol.asyncIterator を使用します。 next() は promise を返す必要があります。 nextが返す値は 通常のイテレータ なら、{ value:任意値 , done:真偽値 }だが、 非同期イテレータ { value:任意値 , done:真偽値 }で解決するPromiseオブジェクト でないといけない。 →async next()とするとpromiseで返却されるし awaitも使えるのでおすすめ このようなオブジェクトをイテレートするには、for await (let item of iterable) ループを使用します。 for awaitとすることで、[Symbol.asyncIterator]のオブジェクトを取得するようになります。 また、繰り返すごとにawait処理を行うようになります。 let range = { from: 1, to: 5, [Symbol.asyncIterator]() { // (1) return { current: this.from, last: this.to, async next() { // (2) // asyc next の中で、 "await" が使えます await new Promise(resolve => setTimeout(resolve, 1000)); // (3) if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; (async () => { for await (let value of range) { // (4) alert(value); // 1,2,3,4,5 } })() ご覧の通り、構成は通常のイテレータと同様です: オブジェクトを非同期的に反復可能にするために、Symbol.asyncIterator メソッドが必要です。 (1) それは promise を返す next() メソッドを持つオブジェクトを返す必要があります。(2) next() メソッドは async である必要はなく、promise を返す通常のメソッドでもよい。今回はasyncを利用して戻り値のオブジェクトをpromiseにラップさせている。 またasyncを利用することで、 中でawait が使えるので便利。 今回は単に1秒だけ遅延させるために利用している。(3) イテレートするために、for await(let value of range) (4) を使用しています。 すなわち、“for” の後に “await” を追加します。 for awaitとすることで、 range[Symbol.asyncIterator]() を一度だけ呼び出すようになり、 返されたオブジェクトに対して next() を呼び出します。 わかんなかったらここみて https://note.affi-sapo-sv.com/js-async-iterators-generators.php#title2
45
非同期なイテラブルに対して …スプレッド演算子はつかえるか?
つかえない。 スプレッド演算子 ... は非同期には動作しません 通常の、同期的なイテレータを要する機能は、非同期イテレータでは動作しません。 例えば、スプレッド演算子は動作しません: alert( [...range] ); // Error, no Symbol.iterator Symbol.asyncIterator ではなく、Symbol.iterator があることを期待しているので、これは当然の結果です。 また、for..of のケースに対しても同様です: await なしの構文は Symbol.iterator を必要とします。
46
ジェネレータ実装するか、イテレータを実装するか?
ジェネレータを使用すると、イテレーションのコードをはるかに短くすることができます。ほとんどの場合、反復可能にしたい場合、ジェネレータを使用します。
47
非同期ジェネレータの処理を前問の処理をもとにおもいっきりリファクタリングせよ let range = { from: 1, to: 5, [Symbol.asyncIterator]() { // (1) return { current: this.from, last: this.to, async next() { // (2) // asyc next の中で、 "await" が使えます await new Promise(resolve => setTimeout(resolve, 1000)); // (3) if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; (async () => { for await (let value of range) { // (4) alert(value); // 1,2,3,4,5 } })()
ジェネレータそのものが、イテラブルである。 つまりSymbol.IteratorやSymbol.asyncIteratorメソッドを実装しているので、思いっきりリファクタリングすることができる。 ジェネレータ生成関数自体に asyncをつけることで実装できる。 async function* generateSequence(start, end) { for (let i = start; i <= end; i++) { // await が使えます! await new Promise(resolve => setTimeout(resolve, 1000)); yield i; } } (async () => { let generator = generateSequence(1, 5); for await (let value of generator) { alert(value); // 1, then 2, then 3, then 4, then 5 (間に遅延をはさみながら) } })(); 非同期なイテレーションを行うには、 for await を利用するのが特徴 また対象としてはジェネレータオブジェクトを指定可能である。 →ジェネレータオブジェクトは、Symbol.asyncIteratorメソッドをサポートしている 内部でnextメソッドが動く。 awaitを付与する必要があり、 返却値はpromiseとなる。 for awaitを利用することで next()によって返されるpromiseが完了することを待ち、promiseでラップされた値やエラーを透過的にあつかってくれる。 ちなみにnextメソッドにて、awaitを利用せずに、 promise.thenでコールバック関数を登録してイテレーションを行う場合は、 then内のコールバック処理では戻り値を返せないので、実装はできない。 thenが戻すpromise(valueに結果をもつ)を戻り値とすればいける。 →関数内で非同期処理の結果が欲しい場合は、promiseで取得する必要がある!! よって、通常のforでは、nextはpromiseを返却してしまう。 for awaitでは、promiseが完了してから実行してくれる上にpromiseでラップされた値をそのまま戻り値として利用させてくれるので、透過的にあつかうことができる よってこのような構文を利用することとなる ほとんどの実践的なアプリケーションでは、一連の値を非同期的に生成するオブジェクトを作成したい場合、非同期ジェネレータが使えます。 構文はシンプルです。function* の前に async をつけます。これでジェネレートが非同期になります。 また、次のようにそれをイテレートするのに、for await (...) を使用します。 非同期ジェネレータの場合、generator.next() メソッドは非同期であり、promise を返します。つまり、yieldはpromiseを返す 通常のジェネレータでは、result = generator.next() を使用して値を取得します。非同期ジェネレータでは、次のように await を追加する必要があります: result = await generator.next(); // result = {value: ..., done: true/false} そういうわけで、非同期ジェネレータは for await...of で動作します。 なるほど、forだと nextメソッドがpromiseを返してしまうので、 for awaitとすることで promiseのvalueをもらうことができるってことか!
48
先ほどの非同期イテレータをベースに非同期ジェネレータでそのまま実装せよ let range = { from: 1, to: 5, [Symbol.asyncIterator]() { // (1) return { current: this.from, last: this.to, async next() { // (2) // asyc next の中で、 "await" が使えます await new Promise(resolve => setTimeout(resolve, 1000)); // (3) if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; } }; (async () => { for await (let value of range) { // (4) alert(value); // 1,2,3,4,5 } })()
Symbol.asyncIteratorは非同期イテレーターの場合はイテレーターオブジェクトを返していた。 非同期ジェネレータの場合は、ジェネレータオブジェクトがnextを実装しているので、ジェネレータを返す必要がある。 →ジェネレータ関数を実行すれば生成できる。 Symbol.asyncIteratorに定義したメソッドはfor awaitしたタイミングで呼ばれるので、この時点でイテレーターオブジェクトではなく、代わりにジェネレータがかえることになる。 Symbol.asyncIteratorメソッドをジェネレータ生成関数として、ジェネレータの定義を行い、非同期を扱えるようにする。 yieldはpromiseを返す。 let range = { from: 1, to: 5, //書き方に注意 async *[Symbol.asyncIterator]() { for(let value = this.from; value <= this.to; value++) { // 値の間に間隔を作り、なにかを待ちます await new Promise(resolve => setTimeout(resolve, 1000)); yield value; } } }; (async () => { for await (let value of range) { alert(value); // 1, then 2, then 3, then 4, then 5 } })(); //Symbol.asyncIteratorプロパティにジェネレータ生成関数をいれる。 async *[Symbol.asyncIterator]() { は [Symbol.asyncIterator]: async function*() { とやってることおなじ
49
非同期ジェネレータの実際のユースケース、 ページネーション機能を以下のコードに記載した。 解説せよ async function* fetchCommits(repo) { let url = `https://api.github.com/repos/${repo}/commits`; while (url) { const response = await fetch(url, { // (1) headers: {'User-Agent': 'Our script'}, // github は user-agent ヘッダを要求します }); const body = await response.json(); // (2) JSON として response をパース(コミットの配列) // (3) ヘッダにある次のページの URL を抽出 let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); nextPage = nextPage && nextPage[1]; url = nextPage; for(let commit of body) { // (4) ページが終わるまで1つずつ yield commits yield commit; } } } 使用例 (async () => { let count = 0; for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) { console.log(commit.author.login); if (++count == 100) { // let's stop at 100 commits break; } } })();
URLからリクエスト→jsonで取得→全てのコミットをyield して外部へ渡す。 また全てのコミットをyieldし終わったら、 ヘッダ情報のnextpageを取得し、 再度urlリクエストから始める。 この時urlがnullなら処理をおえる→done:trueになる。 このループはwhile(url)で実装できる
50
通常のイテレータと非同期イテレータの違いを 反復可能を提供するオブジェクトメソッド next() が返す値 の二つの観点でのべよ
nextから返却される値は画像のとおりだけど、 nextで返却する値(nextメソッド内で返却する値)に関しては、以下の通りに実装する必要がある。 イテレータなら {value:…, done: true/false} 非同期イテレータなら {value:…, done: true/false} に解決する Promise である必要がある。 ちなみにジェネレータはvalueを返すだけで内部的に {value:…, done: true/false} の形式で扱ってくれる。 非同期ジェネレータもvalueを返すだけで、内部的に非同期イテレーターと同様の扱いとなる
51
通常のジェネレータと非同期ジェネレータの違いを 宣言、 generator.next() が返却するものの二つの観点でのべよ
画像の通り generator.next() が返却するものについて、 ジェネレータ内部では、yieldで返すので、 {value:…, done: true/false} この形式にこだわる必要はない
52
非同期ジェネレータを利用する例はなにがあるか?
反復的に非同期処理を行う場合に利用できる。 非同期イテレーターは、nextが呼ばれるたびに非同期処理が実行する場合によいかもしれない。 非同期ジェネレータは、nextが呼ばれるたびに非同期処理を実行してもいいし、しなくてもいい。 前問題のページネーション機能はyield から次のyieldまでの間に毎回非同期処理があるわけではなかった。 チャンクごとに流れるデータを処理する場合とかにつかえる 大きなファイルのアップロードとか 他にはLWCのSOQLの繰り返し取得とかも使えそう 前問題のページネーションを非同期イテレーターで実装はやろうとすると、 nextが呼ばれる度に プロパティからオブジェクトの状態を取得したりしてurlをフェッチするかの判断を行う必要がある。 データ取得済みか判断し、データがあればその要素を返す。※プロパティでカウントアップして次要素も取得できるようにする データが取得してなければ、フェッチしてから、カウンターを初期化して、要素を返すみたいな感じになりそう! Web 開発では、データがチャンクごとに流れるとき、データのストリームを扱うことがよくあります。例えば、大きなファイルのダウンロードやアップロードです。 非同期ジェネレータを使用して、このようなデータを処理することもできます。また、ブラウザなどの一部の環境では、Stream と呼ばれる別の API もあることに注目してください。これは、データを変換してあるストリームから別のストリームに渡す特別なインターフェースを提供しますす(e.g ある場所からダウンロードして、すぐに別の場所に送信する場合)。
53
モジュールとはなにか? どのように利用できるか、二つのキーワードについて説明せよ
モジュールは単なる1つのファイルです。 ディレクティブ export と import を利用することで、モジュール間で機能を相互にやりとりすることができます。: つまり、importする側もモジュールである必要があるということ! export キーワードは、ファイルの外部からアクセス可能であるべき変数や関数にラベル付けをします。 import は他のモジュールから機能をインポートできるようにします。 例えば、関数をエクスポートしているファイル sayHi.js があります: // 📁 sayHi.js export function sayHi(user) { alert(`Hello, ${user}!`); } …そして、別のファイルでそれをインポートして使います。: // 📁 main.js import {sayHi} from './sayHi.js'; alert(sayHi); // function... sayHi('John'); // Hello, John! import ディレクティブは現在のファイルからの相対パス ./sayHi.js のモジュールを読み込み、エクスポートされた関数 sayHi を対応する変数に割り当てます。
54
モジュールをブラウザで利用する際の注意事項をあげよ
モジュールは特別なキーワードと機能を提供するので、<script type="module"> 属性をを使用して、ブラウザにモジュールを扱うことを伝える必要があります。 このようになります: 結果say.jsindex.html <!doctype html> <script type="module"> import {sayHi} from './say.js'; document.body.innerHTML = sayHi('John'); </script> ブラウザは自動的にインポートされたモジュールを取得/評価し、スクリプトを実行します。
55
通常のスクリプトとモジュールの違いの特徴をふたつあげよ
常に “use strict” モジュールは常に use strict です。E.g. 未宣言変数への代入はエラーになります。 <script type="module"> a = 5; // error </script> モジュールレベルのスコープ 各モジュールには独自の最上位のスコープがあります。つまり、モジュール内の最上位の変数や関数は他のスクリプトからは見えません。 <!doctype html> <script type="module" src="user.js"></script> <script type="module" src="hello.js"></script> のときuser.jsは独自のスコープ hello.jsも独自のスコープがあるので、 hello.jsでuser.jsを参照できない。 →hello.js上でuser.jsをインポートせよ import {user} from './user.js'; document.body.innerHTML = user; // John 逆にモジュールじゃない場合は同じ空間に展開されるので 別のスクリプトタグの内容が見れる
56
以下のコードは関数をadmin.jsからエクスポートし、 別々のファイルでインポートしている。 二つ目のファイルでアラート内容はどうなるか? また、メインスクリプトにて外部スクリプトとして1.jsと2.jsをロードし、実行しているとする。 // 📁 admin.js export let admin = { name: "John" }; // 📁 1.js import {admin} from './admin.js'; admin.name = "Pete"; // 📁 2.js import {admin} from './admin.js'; alert(admin.name); // ???
Peteが出力される エクスポートしたならば複数回呼び出しは可能となる!! モジュールは初回にだけ評価され、admin オブジェクトが生成され、その後このモジュールをインポートするすべてのモジュールに渡されます。 すべてのインポータは正確に1つの admin オブジェクトを取得することになります。: これがまさにモジュールが1度のみ実行されるためです。エクスポートが生成され、インポートする側でそれらを共有するため、何かが admin オブジェクトを変更すると、他のインポートしたスクリプトはその変更が見えます。
57
モジュールはどのような場面でつかえるか?
エクスポートが生成され、インポートする側でそれらを共有するため、何かが admin オブジェクトを変更すると、他のインポートしたスクリプトはその変更が見えます。 このような振る舞いは、モジュールを 構成(configure) できるため実際に非常に便利です。 つまり、設定値をモジュールに保存し、モジュールの機能を利用して何か汎用的な機能を実行するのにむいている! 複数ファイルでそれが活用できるので!! モジュールはセットアップが必要な汎用機能が提供できます。例.認証には資格(credential)が必要です。そして、外部のコードが割り当てることを期待した構成用のオブジェクトをエクスポートします。 これは古典的なパターンです: 1. モジュールはいくつかの構成手段をエクスポートします。例.構成オブジェクト 2. 初回インポート時にそれらを初期化し、そのプロパティへ書き込みます。トップレベルのアプリケーションスクリプトがそれを行うかもしれません。 3. 以降のインポートでは、そのモジュールを使用します。 例えば、admin.js モジュールは特定の機能(例. 認証など)を提供するかもしれませんが、外部から admin オブジェクトにクレデンシャル情報が来ることを期待します。: // 📁 admin.js export let config = { }; export function sayHi() { alert(`Ready to serve, ${config.user}!`); } ここで、admin.js は config オブジェクトをエクスポートします(初期は空ですが、デフォルトプロパティもある場合もあります)。 次に init.js 、我々のアプリの最初のスクリプトで、config をインポートし、config.user を設定します: // 📁 init.js import {config} from './admin.js'; config.user = "Pete"; …これでモジュール admin.js は構成されました。 以降のインポートはこれを呼び出すことができ、現在のユーザが正しく表示されます: // 📁 another.js import {sayHi} from './admin.js'; sayHi(); // Ready to serve, Pete!
58
モジュールの実行タイミングについて説明せよ 以下は複数同じファイルをインポートしている。 // 📁 alert.js alert("Module is evaluated!"); // 別のファイルから同じモジュールをインポート //main.js //一回目 import `./alert.js`; //二回目 import `./alert.js`;
一回目のインポート時のみ、アラートを実行する 二回目はなにもされない。 一回目の二回目のファイルが別々でも一回目のみ。 返されるオブジェクトは同一、 今回の例はオブジェクトは取り込まないインポート
59
import.metaとはなにか?
オブジェクト import.meta は現在のモジュールに関する情報を含んでいます。 この内容は環境に依存します。ブラウザでは、スクリプトの url、HTML 内であれば現在のウェブページの url を含んでいます。: <script type="module"> alert(import.meta.url); // script url (インラインスクリプトに対する HTML ページの url) </script>
60
モジュール最上位のスコープでthisを使うと何がかえるか?
モジュールでは、最上位の “this” は undefined です これはささいな特徴ですが、完全性のために言及しておきます。 モジュールでは、最上位の this は undefined です。 this がグローバルオブジェクトである非モジュールスクリプトとの比較です:。 <script> alert(this); // window </script> <script type="module"> alert(this); // undefined </script>
61
以下コードはモジュールタイプのスクリプトと、普通のスクリプトの比較である。 何が異なるか? <script type="module"> alert(typeof button); </script> 以下の通常のスクリプトと比較してください: <script> alert(typeof button); </script> <button id="button">Button</button>
モジュールタイプの場合 object が出力される。つまり、ボタンを参照できる。 モジュールは遅延されるので、スクリプトはページ全体がロードされた後に実行します 通常のスクリプトの場合 Error: button is undefinedとなる。 スクリプトは下の要素は見えません // 通常のスクリプトは、ページの残りが処理される前に即時実行します。
62
インラインスクリプトとはなにか?
インラインでJavascriptを書くときはscript要素内に記述します。 <script type="text/javascript"> //Javascriptを記述 </script> 記述場所は、基本的にHTMLファイル内のどこに記述してもOKです
63
外部スクリプトとはなにか?
Javascriptのコード自体は外部ファイルに記述し、HTML内にはscript要素でファイルを読み込むコードを記述します。 <script src="Javascriptの外部ファイル"></script>
64
外部モジュールスクリプトとはなにか?
外部モジュールスクリプト <script type="module" src="..."> でsrcに指定したもの
65
インラインスクリプトと、外部スクリプトのスクリプト実行タイミングについて、述べよ また、モジュールスクリプトの実行タイミングをのべよ
通常のスクリプト(インライン、外部)は HTMLが上からツリー解析され、<script>タグにあたった時に実行される。 モジュールスクリプトは HTML解析がおわったあとに実行される。 モジュールスクリプトは HTML ドキュメントが完全に準備できるまで待ちます。 副作用として、モジュールスクリプトは常にその下の HTML 要素が見えます。 <script type="module"> alert(typeof button); // object: スクリプトは下のボタンが `見え` ます // モジュールは遅延されるので、スクリプトはページ全体がロードされた後に実行します </script> 以下の通常のスクリプトと比較してください: <script> alert(typeof button); // Error: button is undefined, スクリプトは下の要素は見えません // 通常のスクリプトは、ページの残りが処理される前に即時実行します。 </script> <button id="button">Button</button>
66
モジュールのとこ理解しなおせ! 最後の方わからん! https://ja.javascript.info/modules-intro#ref-184
ひ
67
現実的なモジュールはどのように利用されているか?
現実には、ブラウザモジュールが “生” の形式で使用されることはほとんどありません。 通常、それらを Webpack などの特別なツールを使って一緒にまとめて、プロダクションサーバにデプロイします。 Webpackは複数のJavaScriptファイルを一つのファイルにまとめて出力するツールです。この「一つのファイルにまとめる」ことをバンドル(bundle)と言い、また「モジュールを一つのファイルにまとめて出力するツール」のことをモジュールバンドラー(module bundler)と呼びます。 バンドラーを使用する利点の1つは、それらはモジュールをどのように解決するかについてより多くの制御を与えることができ、CSS/HTML モジュールのようにベアモジュールやその他のことを可能にします。 ↑意味わからん
68
エクスポートの対象はなにか?、
変数、関数、クラスのいずれか であれば、その前に export を置くことで、エクスポート対象として任意の宣言にラベル付けすることができます。 例えば、ここではすべてのエクスポートは有効です: // 配列のエクスポート export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // 定数のエクスポート export const MODULES_BECAME_STANDARD_YEAR = 2015; // クラスのエクスポート export class User { constructor(name) { this.name = name; } }
69
関数宣言やクラス定義時に、最後にセミコロンはいるか?
いらない。 export function sayHi(user) { alert(`Hello, ${user}!`); } // 末尾に ; はありません export class User { constructor(name) { this.name = name; } }
70
宣言とは別でエクスポートするには?
// 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); } function sayBye(user) { alert(`Bye, ${user}!`); } export {sayHi, sayBye}; // エクスポートされた変数のリスト …あるいは、技術的には関数の上に export を置くこともできます。
71
通常のしていしてインポートするほう 記載方法は?
通常は、次のようにインポートするものの一覧を波括弧 import {...} に置きます。: // 📁 main.js import {sayHi, sayBye} from './say.js'; sayHi('John'); // Hello, John! sayBye('John'); // Bye, John!
72
全て一括でインポートするには?
import の数が多い場合、import * as <obj> を使用してオブジェクトとしてすべてをインポートすることができます。例: // 📁 main.js import * as say from './say.js'; say.sayHi('John'); say.sayBye('John'); sayのプロパティあつかいとなる
73
指定する場合 // 📁 main.js import {sayHi, sayBye} from './say.js'; 全部インポートする場合 // 📁 main.js import * as say from './say.js'; の違いはなにか?
全部インポートする場合はasで指定した名前経由で呼び出しが必要 say.sayHi('John'); say.sayBye('John'); 指定してインポートする場合 sayHi('John'); // Hello, John! sayBye('John'); // Bye, John!
74
指定したインポートで異なる名前で取り込むには?
異なる名前でインポートするために as を使うこともできます。 例えば、簡潔にするために sayHi をローカル変数 hi にインポートしましょう。sayBye も同様です。: // 📁 main.js import {sayHi as hi, sayBye as bye} from './say.js'; hi('John'); // Hello, John! bye('John'); // Bye, John!
75
エクスポートする際、別名を指定するには?
// 📁 say.js ... export {sayHi as hi, sayBye as bye}; 今、hi と bye は外部にとって公式な名前になります。: // 📁 main.js import * as say from './say.js'; say.hi('John'); // Hello, John! say.bye('John'); // Bye, John!
76
モジュールには二つの種類がある。 説明せよ
複数の関数をパックしたモジュール 単一のエンティティを宣言したモジュール
77
単一のエンティティを宣言するモジュールはどのようにエクスポートするか? また、これ用の特殊な書き方は?
モジュールは、特別な export default (“デフォルトエクスポート”) 構文を提供し、“モジュール毎に 1 つのもの” のように見栄えを良くします。 エクスポートするエンティティの前に export default を置きます: // 📁 user.js export default class User { // "default" を追加するだけ constructor(name) { this.name = name; } } ファイルごとに 1 つだけ export default が存在する場合があります。 例えば、これらはすべて有効なデフォルトエクスポートです: export default class { // クラス名なし constructor() { ... } } export default function (user) { // 関数名なし alert(`Hello, ${user}!`); } // 変数の作成なしで単一値のエクスポート export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; これは問題ありません。なぜなら export default はファイル毎に1つのみだけだからです。そのため、import は何をインポートすべきか常に知っています。
78
export defaultで指定されたものをインポートするには?
export defaultはインポート時に、名前を指定する必要がある。 その際、波括弧は必要ないので綺麗 // 📁 main.js import User from './user.js'; // {User} ではなく User new User('John');
79
名前付きエクスポートと デフォルトエクスポートのちがいは?
名前付きエクスポートは、インポートした名前を指定すること! import {User} from './user.js'; // import {MyUser} は動作しません。名前は {User} でなければなりません。 …一方、デフォルトエクスポートの場合、インポート時に名前を決めること! import User from './user.js'; // 動作します import MyUser from './user.js'; // 動作します // なにでもインポートでき、それは動作します
80
export function sayHi(user) { alert(`Hello, ${user}!`); } 上記とは違う書き方で、 ある関数の名前を決めないでエクスポート する場合に、export defaultを利用しないやり方で、どうするか?
別でエクスポートするには別名でdefaultキーワードでエクスポートするようにする “default” というキーワードはデフォルトエクスポートを参照するために使用されます。 function sayHi(user) { alert(`Hello, ${user}!`); } // 関数の前に "export default" を追加する場合と同じです export { sayHi as default }; ↑say hi はデフォルトエクスポートとする。 デフォルトエクスポートは1モジュールに一つだけ! それはdefaultという名前で管理されている
81
デフォルトエクスポートとただのエクスポートを一つのモジュールに定義可能か?
モジュール user.js が 1 つのメインの “デフォルト” のものと、いくつかの名前付きのものをエクスポートする場合(めったにありませんが起こりえます)、次のようになります: // 📁 user.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); }
82
以下は一つのモジュールでデフォルトエクスポートとエクスポートを使用している。 これをインポートするには? // 📁 user.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); }
これは名前付きと一緒にデフォルトエクスポートをインポートする方法です: 波括弧を使用してデフォルトキーワードを使用する // 📁 main.js import {default as User, sayHi} from './user.js'; new User('John');
83
以下のエクスポートにたいして、 以下のインポートすると、どのようにUserクラスを扱うことができるか // 📁 user.js export default class User { constructor(name) { this.name = name; } } // 📁 main.js import * as user from './user.js';
* as obj はobjのプロパティに展開される。 デフォルトエクスポートは defaultとして管理されるので、 user.default で参照される。 オブジェクトとして * ですべてをインポートする場合、default プロパティはまさにデフォルトエクスポートです: let User = user.default; new User('John');
84
再エクスポートとはなにか?
“再エクスポート” 構文 export ... from ... を使うと、インポートをした直後にそれらを(場合により別の名前で)エクスポートすることができます。: export {sayHi} from './say.js'; // sayHi を再エクスポート export {default as User} from './user.js'; // default をUserとして再エクスポート
85
再エクスポートが必要なケースをあげよ
パッケージという多くのモジュールを含むフォルダで、一部の機能を外部にエクスポートする際、 単一のエントリポイント経由でパッケージの機能を公開したい時に利用する。 つまり、パッケージを利用したい人は “メインファイル” auth/index.js からのみインポートできるようにするということ。 パッケージのファイル構造は以下みたいなかんじ auth/ index.js user.js helpers.js tests/ login.js providers/ github.js facebook.js ... // 📁 auth/index.js // login/logout の再エクスポート export {login, logout} from './helpers.js'; // User で default エクスポートを再エクスポート export {default as User} from './user.js'; ... 上記は以下内容と同じ! // 📁 auth/index.js // login/logout をインポートし、すぐにエクスポートします import {login, logout} from './helpers.js'; export {login, logout}; // User として default をインポートし、エクスポートします import User from './user.js'; export {User}; ... これで、我々のパッケージの利用者は import {login} from "auth/index.js" ができます。
86
再エクスポートの制限事項は?
デフォルトの再エクスポートは明示的にやる必要がある。 注意: export User from './user.js' は動作しません。実際には構文エラーです。デフォルトエクスポートを再エクスポートするには、明示的に {default as ...} と言及しなければなりません。 // User で default エクスポートを再エクスポート export {default as User} from './user.js';
87
export * from './user.js' をした時に 何が再エクスポート対象とならないか
名前付けエクスポートのみを再エクスポートし、デフォルトは含まれていません。改めて言いますが、明示的に言及する必要があります。 例えば、すべてを再エクスポートするには、2つの文が必要になります。: export * from './module.js'; // to re-export named exports export {default} from './module.js'; // to re-export default デフォルトは再エクスポートするときのみ明示的な言及が必要です。 ちなみにインポートする場合は、 import * as obj でデフォルトエクスポートもインポートできる。 ただ、obj.default としてデフォルトエクスポートをインポートします。 なので、インポートとエクスポートの間には少し非対称性があります
88
エクスポートの種類まとめ
export には以下の種類があります: 宣言の前: export [default] class/function/variable ... スタンドアロン: export {x [as y], ...}. 再エクスポート: export {x [as y], ...} from "mod" export * from "mod" (デフォルトは再エクスポートしない). export {default [as y]} from "mod" (デフォルトの再エクスポート).
89
importのまとめ
Import: モジュールから名前付きエクスポート: import {x [as y], ...} from "mod" デフォルトエクスポート: import x from "mod" import {default as x} from "mod" すべて: import * as obj from "mod" モジュールの取得/評価のみでインポートはしない: import "mod"
90
以下は実行可能か? sayHi(); import {sayHi} from './say.js';
実行可能! import/export 文を他のコードの前後に置くことができ、どちらでも同じ結果になります。 実際には、インポートは通常ファイルの先頭にありますが、それは利便性のためだけです。
91
コードの任意の場所でインポートしたい場合はどうするか?
import式が利用できる。 また引数は、文字列でパスを指定できる。 import(module) 式は、モジュールを読み込み、モジュールがもつすべてのエクスポートを含むオブジェクトをvalueにもつ promise を返します。 コードの任意の場所で動的に利用できます。以下は例です: let modulePath = prompt("Module path?"); import(modulePath) .then(obj => <module object>) .catch(err => <loading error, no such module?>) あるいは、async function 内であれば let module = await import(modulePath) とすることができます。
92
以下を動的インポートせよ 例えば、次のようなモジュール say.js があるとします: // 📁 say.js export function hi() { alert(`Hello`); } export function bye() { alert(`Bye`); }
…動的インポートは次のようにできます: let {hi, bye} = await import('./say.js'); hi(); bye();
93
以下のデフォルトエクスポートを動的インポートする場合はどうするか? // 📁 say.js export default function() { alert("Module loaded (export default)!"); }
そこへアクセスするには、モジュールオブジェクトの default プロパティを使用します。: let obj = await import('./say.js'); let say = obj.default; // あるいは、1行で: let {default: say} = await import('./say.js'); ↑オブジェクトの分割代入 コロンの後の名前で代入する なのでdefaultをsayでとりこむ say();
94
メモ
await はまつけど、その間イベント処理とかは処理される。 awaitしないとそのまままたずに続きの処理がおこなわれる。
95
asyncで定義した関数をそのまま実行するとどのようきうごき、なにがかえるか? また、asyncで定義した関数を実行し、 戻ってきたpromiseに、awaitを使った時の戻り値はどうなるか?
以下憶測である asyncで定義した関数をそのまま実行すると、 内部ではawaitが使われるまで、同期的に処理が動く、 処理が最後まで完了するとfulfilledのpromiseを返す。 ※はじめにpendingのpromiseを返してそう。 awaitの構文はいかのとおり let result = await promise; なので、 let result = await async関数(); となり関数を上記の説明の通り実行する。 promiseが完了するまで他の処理をして待つこととなる。 完了したら、promiseのresultの値がresultに設定され、続きのコードを実行できる。
96
await promise 処理をかく と then(コールバック関数) の動作の違いをのべよ
awaitの下の処理はマイクロタスクキューに入れられない。 ほんとにまってくれるだけ。 なので、解決したプロミスだとそのまま同期的に動く thenのコールバック関数はマイクロタスクキューにはいる! 解決したプロミスでも非同期に実行