卵どもに贈る独学チートシートの続き
前回記事を勢いよく書き出したのは良いものの、途中で書くことが多すぎて挫折、後半部分はサマリーのみで逃亡したわけですが、今いるマニラは長期連休で暇やなあと思っていた時に、あれを書き切ってしまわんといかんと思って戻ってきました。
正直Qiitaなどに駄文を書いたところで何の儲けにもならず、下手を打てば100戦錬磨のプログラマー諸先輩方からの厳しいツッコミに晒されて蜂の巣にされるわけで、リスクしかないわけですが、前回かなりのいいねやストックをしていただいていた方々がおり、続きを書かねばならぬという気持ちになりました。卵のみんな、ありがとな。
では、続きのJavaScriptから。
JavaScript
- JavaScriptの大部分はオブジェクトで出来ている
- APIの理解とJSONの処理
- Promise, async/awaitの理解
- ファンクションはいつ動くのか、同期、非同期処理の理解
前回書いたHTMLやCSSはマークアップ言語であって、プログラミング言語ではない。なぜなら、データの処理はしていないからである。書いたように、文章構造の定義と、装飾である。
で、色々なデータを料理する言語として、JavaScriptである。
ウェブ開発をしていると、おそらく、ここで躓く卵どもが多いと思う。わしも、20年前にウェブをやっていたときは、正規表現やコールバックヘルなどに巻き込まれ、ここで挫折した。
JavaScriptが何やってるかというと、何かをどうにかしてくれるのである。
何とどうの部分を絶えず意識していれば、全く難しくない。
自分探しと同じで、自分が何をどうしたいのか が分かっている事が一番重要で、JavaScriptは所詮、貴様のやりたい事をやってくれる道具であって、それ以上ではない。
で、何の部分には、
- 文字列
- 数字
- ブーリアン(true or false)
- undefined
- null (何も存在しない)
のどれかが入る。
定義型だとかなんとか言われてるけど、結局はこれだけで、もっと整理されたデータやまとまったのデータを扱いたい場合は、ArrayかObjectに上記の値を突っ込んでおけばよい。
objectはシンプルに{key:value}というラベルと値のセットであり、arrayは値だけがズラッと入ってるイメージである。
どうしたいかの部分は、関数/Function と言われている部分で、まさにファンクション(機能)である。
基本の型は超シンプルで、
keyword 名前 (材料) {
材料 で 何かする
}
statement (この時) {
これをする
}
これだけである。
具体的には、
function printName(name) {
console.log(`Hello ${name}`);
}
//もしくはアローファンクションなら
const printName = (name) =>{
console.log(`Hello ${name}`);
}
// if statement なら
const name = "Goku"
if (name === "Goku") {
console.log(`Ossu! ${name}`)
} else {
console.log(`Genkidesuka? ${name}`)
}
// for statement なら
for (let i=0; i<=5; i++) {
console.log('Ossu!');
}
で、functionとif statementなんかを組み合わせて、さらにクラスを定義して犬小屋を作るとしたら、
// 犬小屋クラスを定義
class DogHouse {
constructor(color, size) {
this.color = color;
this.size = size;
}
// 犬小屋を建てるメソッド
build(budgetIsEnough) {
return new Promise((resolve, reject) => {
if(budgetIsEnough){
resolve(`${this.color}色の${this.size}サイズの犬小屋がでけた!`);
} else {
reject("予算不足です");
}
});
}
}
// 犬小屋を建てるファンクションを定義
const buildDogHouse = (color, size, budget) => {
const myDogHouse = new DogHouse(color, size);
return myDogHouse.build(budget);
}
// 犬小屋を建てるファンクションをトリガー
const dogHouse = buildDogHouse('金', '大きい', true)
console.log(dogHouse); // 金色の大きいサイズの犬小屋がでけた!
みたいなノリである。
color,size,budget などの、何を とbuild(),buidDgoHouse()の どうする で出来ている。クラスとオブジェクトに関しては、下記で説明しておく。
JavaScriptを書くときは、常に何をどうしたいのか を日本語で考えておく事。
例えば、"user objectが入ったarray" を "mapでループし" て <div className="userList>
の中 に "名前とID" を "表示する" みたいな感じ。
日本語(何であれ人間語)で書けるものは、ほとんどをコードに出来る。出来ないのは、あなたの言葉が破綻しているのだ。
コードを書くコツとしては、まずコメントで、何をしたいか書き出し、具体的なコードはGoogle先生か、ChatGPT教授にでも聞けば助けてくれる。
ChatGPTなどはコードの生成もしてくれるが、お前自身が何をしたいかよく分からなかったり、聞きたい事が正確でないと、返ってくるコードも間違ったコードしか返ってこない。
例えば、下記は先ほど書いていたコードなんだけど、ユーザーがサインインして新しいuser documentがデーターベースに作られたら、そのdocument内のrefCodeの値をゲットして、それとマッチするドキュメントを探し、そのドキュメントのUIDをサインインしたユーザーのドキュメントに保存みたいな処理。
全体見ると、結構複雑なんだけど、1工程づつ書き出していけば、自分でやりたい事が理解しやすい。で、ここに実際のコードを埋めていけば良い。
ちなみにこれは、いわゆるネズミ講みたいに、サインインしたユーザーと、そのユーザーを紹介したユーザーを結び付ける処理。
// Step 1: When the new user document created, fetch document
// Step 2: get refCode in user document
// Step 3: Find the matched document by refCode
// Step 4: Find the uid from the matched document
// Step 5: Save the referer's UID to the created user's document
で、コードを埋めていくとこんな感じになる。長めのコードでも、1行づつ解決していくと、それほど難しくない。
// Step 1: When the new user document created, fetch document
exports.updateReferenceOnUserCreation = functions.firestore.document("users/{userId}").onCreate(async (snap, context) => {
const userDoc = snap.data();
if (!userDoc) {
console.log("No Documents Found");
return null;
}
// Step 2: get refCode in user document
const userRefCode = userDoc.referenceCode.refCode;
const userMyRefCode = userDoc.referenceCode.myRefCode;
if (!userRefCode) {
console.log("No reference code found for the user");
return null;
}
// Step 3: Find the matched document by refCode
const querySnapshot = await db.collection("users").where("referenceCode.myRefCode", "==", userRefCode).get();
// Step 4: Find the UID from the matched document
let matchedRefID;
if (querySnapshot.empty) {
matchedRefID = process.env.OWNER_UID;
} else {
const matchedDoc = querySnapshot.docs[0];
matchedRefID = matchedDoc.data().uid;
}
// Step 5: Save the referer's UID to the created user's document
await snap.ref.update({
"referenceCode.referer": matchedRefID,
});
JavaScriptの大部分はオブジェクトで出来ている
で、上でも書いたように、JavaScriptはデータを色々と処理してくれる。
例えば、ユーザーが書いたQiitaの記事のデータを、サーバーに送る、サーバーから返ってきたデータを、加工して表示するとか。そのほか色々お前ら次第。
で、そのデータっていうのは、上に書いた、テキスト、数字、ブーリアン値、null値の4種類であり、例えばフォームなら、名前、メール、住所、電話番号とか、一個一個処理してられないよね。なので、ObjectとかArrayにまとめてパッケージングしておけば扱いやすい。
APIを介したデータの送受信、各種のライブラリ、JavaScriptそのものや、前回説明したHTMLのDOMに至るまで、ほぼ全てがオブジェクトという形でパッケージされていると言っても過言ではなく、HTTPを使ったサーバーとフロントとのやり取りも、JSONというオブジェクトである。
JSONは、サーバーとのやりとりは文字列データしか使えないため、JavaScriptのオブジェクトを文字列変換したものに過ぎない。
なので、JavaScriptを理解しようと思えば、Objectを常に意識するのが、理解の近道だ。
オブジェクトについては、以前記事に書いたので、これを読んで欲しい。
APIの理解とJSONの処理
こちらにもAPIについて書いたけど、ウェブ上のサービスの根幹がAPIで成り立っている。
すごく簡単なイメージとしては、ウーバーイーツである。
住所(URI)と注文(Request body)を送れば(HTTP Request)、レストランのキッチン(サーバー)が注文を作り、それをドライバーが受け取って送り返してくれる(HTTP Response) 。あとは、食うだけである。
やってることは、注文をだすのと、受け取りである。
料理はレストランのシェフが勝手に作ってくれるし、自分はレシピや材料を知る必要がない。
で、その注文と受け取った飯というのは、先に書いたJSON、つまりオブジェクトの形になって送受信される。
サーバーサイドのプログラムとは、このキッチンのオペレーションを作る事である。
例えば、ここのエンドポイントにアクセスすると、ダミーのブログ記事のデータがJSONで返ってくるのが見れる。
https://jsonplaceholder.typicode.com/posts
で、この返ってきたJSONデーターを、フロント側のHTMLに流し込めば、ブログページの出来上がりである。
https://jsonplaceholder.typicode.com
が住所、 /posts
が注文の部分である。
同期、非同期処理とPromise
同期、非同期の理解は超重要で、超簡単である。
なぜなら、わしらは普段普通にやっているからだ。
元々のJavaScriptは、同期処理と言って、上のコードから順番にコード処理していっておった。
で、これではあかんとなったわけだ。
晩飯に豚汁、チャーハン、焼きサバを作る所をイメージしてみて欲しい。
まず、飯を炊く。で、同期処理だと、飯が炊けるまで豚汁の具を準備することも、サバを焼くことも出来へん。
やっと飯が炊けたら、次は豚汁の具を切り、煮込むわけだが、具材に火が通るまで、鍋の前で待っていなければならず、チャーハンの準備も出来へん!自分の飯なら良いが、これが茶店のランチとかだったら大災害である。
グーグルマップとかに、「昼休みを返せ」とか、「一生行きません」とか「はよ潰れろ」などという心無いレビューが殺到することが目に見えている。こんなんじゃあかん。
で、Promiseと非同期処理である。
飯を炊飯器にセットしておくと、飯が炊けたら、炊けましたっていうブザーなりなんなりの合図が来るやん。それがまさにプロミスである。
で、その合図が来るまでに、例えば豚汁の具を刻む、鍋で煮る。鍋が煮立った頃に、これまた知らせてくれる。まさにプロミス。
プロミスは、その処理が成功か失敗か、途中なのかのステータスと、処理が成功した場合は、その成果物(ここでは炊けた飯、煮えた豚汁)を返してくれる。
もし、炊飯器が破壊されて飯が炊けんかった場合は、ちゃんと”炊飯器爆破”というエラーを返してくれる。
おかげで、他の作業をしていても、プロミスが成功し、炊けた飯が返ってきたタイミングで、チャーハンにとりかかれるというわけや!!つまり、非同期で、各作業が別々に同時的に行われている。
で、サバが焼ける頃には、豚汁はちょうど良い具合になっており、チャーハンも出来上がっているという”晩飯を作る”というタスクがコンプリートする。
上に書いたように、Promiseっていうのは、上から下のフローから自由になり、作業が完了したら合図と成果物を返してくれるっていう、すごく便利な関数なわけだ。
以下は典型的なPromiseを作るコード。100歳以上を切り捨て、エラーを返す処理である。
function promiseFunction(age) {
return new Promise((resolve, reject) => {
if (age < 100) {
resolve("お前は、まだイケる");
} else {
reject("お前はもう、死んでいる");
}
});
}
promiseFunction(101)
.then(result => console.log(result))
.catch(error => console.error(error));
new Promise でプロミスオブジェクトを作り、return でそのオブジェクトを返している。
で、Promiseのコンストラクタは(resolve, reject)の2種類のコールバック関数を引数にとると決まってる。
resolve()は、成功した場合の値を返し、reject()は、エラーが発生した場合にこの値を返す。
簡単に言うと、処理が成功した場合はresolve()がトリガーされ、失敗したらreject()がトリガーされる。
Async/Await
で、このプロミスの性質を利用したのが、ASYNC/AWAIT構文。
問えば、上記のチャーハンを作る関数を考えてみると、
const cookFriedRice = async() => {
try {
const rice = await cookRice();
const friedRice = await fryRice(rice);
console.log("Oishi!!!");
return friedRice;
} catch(err) {
console.log(err.message);
throw err;
}
}
となる。
Await をプロミスを返す関数の前に付けると、その作業が終わるまで次のコードを実行せずに待ってくれる。
つまり、ライスが出来たら、次のfryRice()に渡せるようにする。もし、これを普通の関数として実行すると、ご飯が炊ける前に、fryRice();関数が実行され、飯無しチャーハンが出来てしまう。
なぜなら、cookRice()もfryRice()も非同期関数で、上から下のフローとは無関係に動いているからである。非同期ファンクションをオーダー通りに実行させたい時に、async/await を使う。ちなみに、プロミスを返さない普通の同期ファンクション、例えばconsole.log にawait を付けても、何も起きない。
で、このasyncを付けたcookFriedRice関数自体もプロミス関数と化す。
つまり、チャーハンが出来た時点で、fullfiled か rejectを返してくる。また、friedRice をreturn しているので、調理できたらチャーハンが返ってくる。
晩飯の例で言うと、
Promise.all([cookFriedRice(), cookSaba(), cookTonjiru()])
.then(results => {
console.log('Dinner completed successfully', results);
eat(results);
})
.catch(error => {
console.error('Dinner failed to resolve', error);
});
とすると、全部の料理が出来たら、食べる という処理が出来る。
普段ワシらが普通にやってるマルチタスクなんで、特に理解が難しいわけではないと思うんじゃ。
最後に、自分がJavaScriptの勉強でメチャ役に立った教材を。
Net Ninja のチュートリアルにハズレなし。
この計算機アプリのチュートリアルは、JavaScriptの理解に個人的にメチャ役立った。
Reactなんかのフレームワークを使う際、Array functionは必須。メチャよく使う。
あかん。。。。ここまで書いたらまたしんどくなってきた。JavaScriptしか書けんかった。。。サーバーとフレームワークはまた次回という事で許して。。。ゲフっッッッッっ