1️⃣
【4言語】 ToDoApp つくってみた、い!~JavaScript編~
はじめに
以下の記事の続きです。
今回はJavaScript編です。
コード本編
const fs = require("fs");
const FNAME = "todos.json";
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function load() {
if (!fs.existsSync(FNAME)) return [];
try {
const data = fs.readFileSync(FNAME, "utf-8");
return JSON.parse(data);
} catch (e) {
console.log("Failed to load,starting fresh:", e);
return [];
}
}
function save(todos) {
try {
fs.writeFileSync(FNAME, JSON.stringify(todos, null, 2), "utf-8");
} catch (e) {
console.log("Failed to save:", e);
}
}
function listTodos(todos) {
if (todos.length === 0) {
console.log("No todos.");
return;
}
todos.forEach((t, i) => {
const status = t.done ? "[x]" : "[ ]";
console.log(`${i + 1}.${status} ${t.text}`);
});
}
function addTodo(todos, input) {
const text = input.trim();
if (!text) {
console.log("empty-ignored");
return;
}
todos.push({ text, done: false });
console.log("added");
}
function deleteTodo(todos, index) {
if (index < 1 || index > todos.length) {
console.log("out of range");
return;
}
const removed = todos.splice(index - 1, 1);
console.log("Removed", removed[0].text);
}
function toggleDone(todos, index) {
if (index < 1 || index > todos.length) {
console.log("out of range");
return;
}
todos[index - 1].done = !todos[index - 1].done;
console.log("Toggled.");
}
function main() {
let todos = load();
function menu() {
console.log("\n1:add 2:list 3:delete 4: toggle done 0:exit");
rl.question(">> ", (cmd => {
if (cmd === "1") {
rl.question("Add :", (text) => {
addTodo(todos, text);
save(todos);
menu();
});
} else if (cmd === "2") {
listTodos(todos);
menu();
} else if (cmd === "3") {
rl.question("Delete index: ", (num) => {
deleteTodo(todos, Number(num));
save(todos);
menu();
});
} else if (cmd === "4") {
rl.question("Toggle index: ", (num) => {
toggleDone(todos, Number(num));
save(todos);
menu();
});
} else if (cmd === "0") {
save(todos);
console.log("Bye.");
rl.close();
} else {
console.log("Unknown command");
menu();
}
}));
}
menu();
}
main();
解説編
- 道具の準備~ Modules ~
- require : 外部の機能を取り込むための文言
- fs : FileSystem の略。PC 内のファイルを読み書きするのに使う
- readline : ターミナルでユーザからの入力を受け取るのに使う
- process.stdin : キーボード入力
- process.stdout : 画面出力
- ファイルの読み書きと JSON
- JSON.parse/JSON.stringify : プログラムの中では、オブジェクト、ファイルに保存するときはテキストにする必要がある
- fs.writeSync/fs.readFileSync : ここで読み書き
- 配列メソッド
- push : 配列の末尾にデータを足す
- forEach : 要素を 1 つずつ取り出して処理する(for 文に近い)
- splice : 指定したインデックスから指定した数の要素を削除。そして、削除された要素を配列として返す
- 再起とコールバック
- main 関数の中に menu 関数があり、その中でさらに menu()を呼びだす(再帰)
- rl.question は、質問をしてユーザが Enter を押した後で、{}の中のコードが動く(コールバック)
- try-catch
- ファイルの中身が壊れていたり、JSON として読み込めなかったりする場合、クラッシュさせずに、空のリストとして再開させる
- ユーザへの配慮
- 入力チェック(ガード処理) : 空っぽの TODO を追加させない、存在しない番号を消そうとしたら止める
追加解説
Q.頻出する"Sync"って何?
A.同期処理の事、その処理が終わるまで、次の行に進まずに待機する。確実にその処理を行ってから、次に進みたいときに使う。
Q.同期/非同期,コールバックって何?
A.同期処理: ある処理が完了するまで、次の処理は必ず待つ。
非同期処理: 待たずに別行動。
コールバック: 非同期処理における、時間のかかる処理の完了報告の仕組み。コールバック関数の中に、待っていた処理を記述することが多い。
Q.fs.existsSyncはなぜ非推奨なのか
A.同期処理であるため、ここの処理で長期ストップしてしまうと、アプリケーション全体のパフォーマンスが低下する可能性があるため。このため、同期版のfsメソッドはメイン処理では使わないことが多い。
(ベストプラクティスとしては、try-catchもしくは、fs.promises.access()を使用する。)
Discussion
Promise.withResolvers を使って Promise に変換しがちですね。複数回呼ばれるなら ReadableStream の内部コントローラを使って for await ... に変換しがち
コメントありがとうございます!
Promiseがよく理解できてなかったので、調べなおしてみました!!
そもそもpromiseとは、
非同期処理のためのオブジェクト。より読みやすく管理しやすくするために導入された。
この時のresolveとrejectがコールバック
流れ
executorは自動的に実行を行い、ジョブの実行を試みる。
ジョブが完了したとき、resolveが呼ばれる
エラーの場合にはrejectが呼ばれる
Promise.with.Resolvers()とは、
Promise APIに追加された静的メソッド。"非同期処理をラップするpromiseを手動で作成し、
その制御を外部で行う"ことがより簡潔に記述できる
従来のpromiseとPromise.with.Resolvers()
なるほど...?
理解できたようなあいまいなような...
参考資料も添付してくださって本当にありがとうございます!!
まだまだ勉強頑張ります!!
例えば function a(callback) な 一回呼ぶと 必ず1回 非同期に callback が実行される API がある場合
みたいに書きますが、 Promise.withResolvers() を使うとこう書けます。
例えば function c(callback) な 一回呼ぶと 複数回非同期に callback が実行される API がある場合
みたいに書きますが、 ReadableStreamの内部コントローラをうまい様にすると……
ちょっと実装書くと長くなるので
readable-stream-with-safe-resolversを使います。の様にそれぞれネストを浅く、直列的に書くことができます。
ちなみに Promise.withResolvers() の実装自体はとても簡単なので必要に応じて手癖で ポリフィルを書いたりします。