diff --git a/i18n/ja.json b/i18n/ja.json index 92c4df18..db4a6a6e 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -22,12 +22,14 @@ "FUNCTIONS": "関数", "FUNCTION ARGUMENTS": "関数の引数", "ARROW FUNCTIONS": "アロー関数", + "CLOSURE": "クロージャ", "DESTRUCTURING": "分割代入", "CALLBACK": "コールバック関数", "PROMISE": "Promiseオブジェクト", "PROMISE ALL": "Promise.all", "ASYNC AWAIT": "async/await", "SCOPE": "スコープ", - "USE REGEX LUKE": "正規表現を使え、ルーク" + "USE REGEX LUKE": "正規表現を使え、ルーク", + "FETCH": "Fetch API" } } diff --git a/lib/problem.js b/lib/problem.js index 8babe453..e0897323 100644 --- a/lib/problem.js +++ b/lib/problem.js @@ -1,4 +1,5 @@ const path = require("path"); +const fs = require("fs"); const getFile = require("./get-file"); const compare = require("./compare-solution"); @@ -16,13 +17,40 @@ module.exports = function createProblem(dirname) { workshopper.i18n.lang() === "en" ? "" : "_" + workshopper.i18n.lang(); this.problem = { file: path.join(dirname, "problem" + postfix + ".md") }; this.solution = { file: path.join(dirname, "solution" + postfix + ".md") }; - this.solutionPath = path.resolve( + + // Check for the existence of index.[js, mjs, cjs] + const jsSolutionPath = path.resolve( __dirname, "..", "solutions", problemName, "index.js", ); + const mjsSolutionPath = path.resolve( + __dirname, + "..", + "solutions", + problemName, + "index.mjs", + ); + const cjsSolutionPath = path.resolve( + __dirname, + "..", + "solutions", + problemName, + "index.cjs", + ); + // Set solutionPath to the first existing file + if (fs.existsSync(jsSolutionPath)) { + this.solutionPath = jsSolutionPath; + } else if (fs.existsSync(mjsSolutionPath)) { + this.solutionPath = mjsSolutionPath; + } else if (fs.existsSync(cjsSolutionPath)) { + this.solutionPath = cjsSolutionPath; + } else { + throw new Error(`No solution file found for problem: ${problemName}`); + } + this.troubleshootingPath = path.join( __dirname, "..", diff --git a/menu.json b/menu.json index 6e4c690a..c1f67d59 100644 --- a/menu.json +++ b/menu.json @@ -28,8 +28,10 @@ "ASYNC AWAIT", "ARRAY FILTERING", "ARRAYS MORE", + "FETCH", "FIZZBUZZ", "USE REGEX LUKE", "SCOPE", + "CLOSURE", "PAGINATION" ] diff --git a/package-lock.json b/package-lock.json index 9da0ed64..c0326bf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@recruit-tech/javascripting", - "version": "3.0.3", + "version": "3.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@recruit-tech/javascripting", - "version": "3.0.3", + "version": "3.0.4", "license": "MIT", "dependencies": { "colors": "1.4.0", diff --git a/package.json b/package.json index 713ce764..4f2cec22 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@recruit-tech/javascripting", "description": "Learn JavaScript by adventuring around in the terminal.", - "version": "3.0.3", + "version": "3.0.4", "repository": { "url": "https://github.com/recruit-tech/javascripting.git" }, diff --git a/problems/booleans/problem_ja.md b/problems/booleans/problem_ja.md index 04742e78..7b520a4c 100644 --- a/problems/booleans/problem_ja.md +++ b/problems/booleans/problem_ja.md @@ -83,7 +83,7 @@ false || f(); // f is called! `booleans.js` ファイルを作り、以下のコードをコピーしてください。 ```js -console.log((0 === 1) === ""); +console.log((0 == 1) === ""); console.log((0 === 1) === ""); console.log((10 == "10") === ""); @@ -100,7 +100,7 @@ console.log((0 || 10) === ""); console.log((1 || 10) === ""); ``` -全ての `console.log` による出力が `true` となるように、`''` と書かれた場所を書き換えてください。 (文字列である必要はありません。) +全ての `console.log` による出力が `true` となるように、`''` と書かれた場所を書き換えてください。 (文字列である必要はありません。) 次のコマンドを実行し、あなたのプログラムが正しく動くか確認しましょう。 diff --git a/problems/closure/problem_ja.md b/problems/closure/problem_ja.md new file mode 100644 index 00000000..ca514341 --- /dev/null +++ b/problems/closure/problem_ja.md @@ -0,0 +1,56 @@ +JavaScriptでは、関数定義の内側から外側の変数を参照できます。 + +```js +const makeCounter = () => { + // makeCounterを実行すると、count変数が宣言される + let count = 0; + // count変数は、incrementの関数定義の外側にある + + // incrementの関数定義 + const increment = () => { + // incrementの関数定義の内部にcount変数はない + + count++; // 外側のcount変数を参照して更新できる + return count; // 外側のcount変数を参照してreturnできる + }; + + return [increment]; +}; + +const [increment] = makeCounter(); +console.log(increment()); // 1 +console.log(increment()); // 2 +``` + +このような動作は、クロージャ(定義時の外側の環境を知っている関数)によって実現されますが、詳細な説明は割愛します。ここでは「JavaScriptの関数では、定義の内側から外側の変数を参照できる」ということがわかっていれば大丈夫です。 + +また、`increment`実行時に「`makeCounter`の実行が終了しているにもかかわらず`count`変数を利用できる」ことに驚くかもしれません。 + +一部の言語(C言語など)では、関数内で定義された変数は、その関数の実行が終了すると同時に消えてしまって利用不可能になります。しかしJavaScriptでは、関数によって参照される可能性のある変数は消えません。 + +## やってみよう + +`increment`(プラス1)だけではなく、`decrement`(マイナス1)もできるカウンターを作りましょう。 + +`closure.js`ファイルを作成し、以下のテンプレートをコピペしてコードを書いてみてください。 + +```js +// テンプレート +const makeCounter = () => { + // ここにコードを書く +}; + +// 以下は変更しない +const [increment, decrement] = makeCounter(); +console.log(increment()); // 1 +console.log(increment()); // 2 +console.log(decrement()); // 1 +console.log(decrement()); // 0 +console.log(increment()); // 1 +``` + +コードが書けたら次のコマンドを実行し、あなたのプログラムが正しく動くか確認しましょう。 + +```bash +javascripting verify closure.js +``` diff --git a/problems/closure/solution_ja.md b/problems/closure/solution_ja.md new file mode 100644 index 00000000..a56110ee --- /dev/null +++ b/problems/closure/solution_ja.md @@ -0,0 +1,7 @@ +--- +# 検証成功です! + +カウンターが動作しました。 + +コンソールで `javascripting` コマンドを実行します。次の課題を選択しましょう。 +--- diff --git a/problems/fetch/problem_ja.md b/problems/fetch/problem_ja.md new file mode 100644 index 00000000..0f44e851 --- /dev/null +++ b/problems/fetch/problem_ja.md @@ -0,0 +1,47 @@ +Fetch API は JavaScript で HTTPリクエストを実行するためのAPIです。 +`fetch` 関数の戻り値は `Promise` です。第一引数に URL、第二引数には任意でリクエストのメソッド(デフォルトは `GET`)、ヘッダー、リクエストボディなどを設定できます。 +以下のコードは使用例です。 + +```js +// async awaitを利用する場合 +async function fetchData() { + const response = await fetch("https://example.com"); + // 200 – 299 の範囲外のステータスの時 + if (!response.ok) { + throw new Error(`Error! Status: ${response.status}`); + } + // レスポンスのボディをJSONとして解析 + const data = await response.json(); + return data; +} + +// fetchData関数を呼び出す +const data = await fetchData(); +``` + +`fetch` 関数の戻り値の `Promise` が解決されると、HTTPレスポンスが `Response` オブジェクトとして取得できます。 +この `Response` オブジェクトはいくつかのメソッドを持っています。例えば `.json()` メソッドは、レスポンスボディの中身を JSON として解析し、オブジェクトなどの結果を `Promise` で返します。 + +## やってみよう + +`fetch.mjs` ファイルを作りましょう。 + +今回は、https://dummyjson.com というサイトを利用して、HTTPリクエストを送ってみましょう。 +下記は、https://dummyjson.com へのリクエストとレスポンスの例です。https://dummyjson.com/todos/1 を GET すると、ステータスコードが200で、ボディが「Todoを表すJSON文字列」であるレスポンスが得られます。 + +```js +await fetch("https://dummyjson.com/todos/1"); + +/* response body */ +// {"id":1,"todo":"Do something nice for someone you care about","completed":false,"userId":152} +``` + +`console.log()` を使って、https://dummyjson.com/todos/1 のレスポンスボディの一部である `todo` プロパティの値を表示しましょう。 + +次のコマンドを実行し、あなたのプログラムが正しく動くか確認しましょう。 + +```bash +javascripting verify fetch.mjs +``` + +ここでは詳しくは述べませんが、ファイル拡張子を `.mjs` にすることで Top-level awaitを利用できます。(Top-level await を使用しているファイル全体が1つの大きな `async` 関数のように機能します。) diff --git a/problems/fetch/solution_ja.md b/problems/fetch/solution_ja.md new file mode 100644 index 00000000..95ecb947 --- /dev/null +++ b/problems/fetch/solution_ja.md @@ -0,0 +1,7 @@ +--- +# fetchを使ってデータを取得できましたね! + +これでこの演習は終わりです。 + +コンソールで `javascripting` コマンドを実行します。次の課題を選択しましょう。 +--- diff --git a/problems/null-undefined/problem_ja.md b/problems/null-undefined/problem_ja.md index 16f7f31d..9c8e360f 100644 --- a/problems/null-undefined/problem_ja.md +++ b/problems/null-undefined/problem_ja.md @@ -96,7 +96,7 @@ console.log((false || "default") === ""); console.log((false ?? "default") === ""); ``` -全ての `console.log` による出力が `true` となるように、`''` と書かれた場所を書き換えてください。 (文字列である必要はありません。) +全ての `console.log` による出力が `true` となるように、`''` と書かれた場所を書き換えてください。 (文字列である必要はありません。) 次のコマンドを実行し、あなたのプログラムが正しく動くか確認しましょう。 diff --git a/problems/promise-all/problem_ja.md b/problems/promise-all/problem_ja.md index 19db1585..60ae05f3 100644 --- a/problems/promise-all/problem_ja.md +++ b/problems/promise-all/problem_ja.md @@ -20,7 +20,7 @@ Promise.all([ `Promise.any` 関数では引数の配列の要素内の全ての `Promise` の解決のを待つのとは逆に、どれかひとつが解決されることで返り値の `Promise` も解決されます。 返り値の `Promise` によって解決される値は、引数の配列の要素の `Promise` の中で一番最初に解決された値になります。 -また、配列内の `Promise` のうちのいづれかが拒否されると返り値の `Promise` も拒否されます。 +また、配列内のすべての `Promise` が拒否されると返り値の `Promise` も拒否されます。 ```js // resolved after 1000ms diff --git a/solutions/booleans/index.js b/solutions/booleans/index.js index 31e15a35..2a8f42bc 100644 --- a/solutions/booleans/index.js +++ b/solutions/booleans/index.js @@ -1,4 +1,4 @@ -console.log((0 === 1) === false); +console.log((0 == 1) === false); console.log((0 === 1) === false); console.log((10 == "10") === true); diff --git a/solutions/closure/index.js b/solutions/closure/index.js new file mode 100644 index 00000000..f3195f3e --- /dev/null +++ b/solutions/closure/index.js @@ -0,0 +1,23 @@ +const makeCounter = () => { + let count = 0; + + const increment = () => { + count++; + return count; + }; + + const decrement = () => { + count--; + return count; + }; + + return [increment, decrement]; +}; + +// 以下は変更しない +const [increment, decrement] = makeCounter(); +console.log(increment()); // 1 +console.log(increment()); // 2 +console.log(decrement()); // 1 +console.log(decrement()); // 0 +console.log(increment()); // 1 diff --git a/solutions/fetch/index.mjs b/solutions/fetch/index.mjs new file mode 100644 index 00000000..becac955 --- /dev/null +++ b/solutions/fetch/index.mjs @@ -0,0 +1,13 @@ +// Top-level await +const todo = await fetchTodo(); +console.log(todo); + +async function fetchTodo() { + const response = await fetch("https://dummyjson.com/todos/1"); + // 200 – 299 の範囲外のステータスの時 + if (!response.ok) { + throw new Error(`Error! Status: ${response.status}`); + } + const data = await response.json(); + return data.todo; +}