npm パッケージを公開する

npm を公開するために npm レジストリを使用する。以下に使用するための流れをまとめておく。

  1. アカウント登録

    npm レジストリを使うためにアカウント登録を行う。 登録ページ にアクセスする。手順はこちらが参考になる。

  2. アカウント確認

    コマンド上で npm login を叩き、ログインできるかのテストをする。ユーザ名やパスワード、メールアドレス、2要素パスワードなどが聞かれるので回答する。

     % npm login
     npm WARN adduser `adduser` will be split into `login` and `register` in a future version. `adduser` will become an alias of `register`. `login` (currently an alias) will become its own command.
     npm notice Log in on https://registry.npmjs.org/
     Username: goruchan
     Password: 
     Email: (this IS public) 
     npm notice Please use the one-time password (OTP) from your authenticator application
     Enter one-time password: 
     Logged in as goruchan on https://registry.npmjs.org/.
    

    ログインが完了したら、コマンド上で npm whoami を叩きログインしたユーザ名が表示されることを確認する。

     % npm whoami
     goruchan
    
  3. パッケージファイルを生成する

    パッケージを作成するディレクトリに移動して、npm init を叩いてパッケージファイル(package.json)を生成する。色々聞かれるが、name, version 属性は必須なので入力する。

  4. パッケージをパブリッシュする

    パッケージファイルを生成したディレクトリで、npm publish . を叩く。2要素認証を設定していると、認証を求められるので回答する。

  5. パブリッシュしたパッケージを使う

    適当なディレクトリで下記を実行して、npm が install されるかを確認する

     % npm install <作ったnpm名>
    

参考

JavaScript でHTMLページのソースを取得する

async/await を使う場合は次のような感じ。async 関数はプロミスを返すので、受け取り側で値を取るときには、.then で受け取ればいい🧐

async function GetHtml(url) {
  const promise = fetch(url)            // Promiseを返す
  const response = await promise;       // fetch が確定するまで待機
  return response.text();               // Text表現の解析結果を値としてプロミス返す
}

console.log('foo');
GetHtml('https://bootcamp.fjord.jp/')
.then((text) => console.log(text));     // async はプロミスを返すので .then で受け取る
console.log('hoge');

実行結果は、GetHtml が非同期なので次のような感じになる。

foo
hoge
HTMLの内容(https://bootcamp.fjord.jp/のHTML)

async/await を使わずに書く場合は、次のように書けば同じ結果を得られる。

console.log('foo');
fetch('https://bootcamp.fjord.jp/')
.then((response) => {
    return response.text();
})
.then((text) => {
    console.log(text);
})
console.log('hoge');

スクラムがうまくいっていないと感じる時の改善術

ゾンビスクラムサバイバイルガイドを読んだので、大事だと思ったところをまとめる。

全てのスクラムイベントは、 機能不全に陥らせかねない人間的な要因をはらんでいる。全てのスクラムイベントが、生産的で、魅力的でやりがいのある、楽しいものにするためには、十分にファシリテートする必要がある。

スクラムの目的

スクラムの目的を理解する鍵は、スクラムガイドにある『複雑で変化の激しい問題(複雑で適応的な問題)』という言葉にある。

  • 問題

    必要なことを実行したり知ったりすることを妨げること

  • 複雑、適応的な問題

    『複雑』は、問題を分析して考えるだけでは解決策が見つからないことを意味する。多く要素が絡み合うことで前もって予測することが難しい。その要因には、多くの人や観点が影響することにあり、避けることは難しい。

    上記の対する戦略として、よくあるのは『問題を詳細に分析し、考えすぎる』ということ。複雑な問題に対しては、前もって予測することは困難であり効果がない。複雑な問題が本質的に制御不能で不確実であるという事実は変わらない。

スクラムフレームワークは、 効率的であることよりも効果的であることに重きを置いている。効率は、たくさんの作業(アウトプット)に関することで、効果は作業価値と有用性に関すること。

なぜステークホルダーを巻き込むのか?

組織は価値のあるものを世に出して初めて存続できる。うまくいっていないスクラムは大抵ステークホルダーが絡んでいない。

ステークホルダーは誰か?

次の質問が役に立つ

  • 普段からプロダクトを使う、もしくは使う予定がある
  • プロダクト開発にたくさん投資しているか
  • このプロダクトが扱う課題解決に時間とお金を投資している

上記の人たちは、プロダクトによるリターンが重要なので、次に取り組む価値のあることを助言してくれる。

上記の人たちに対して、早期にプロダクトの価値について仮説検証を行うことで、本当に価値のあるプロダクトを提供できる。

健全なスクラムチーム

自分たちの作業がどれだけ効果的か、つまり作業がステークホルダーや組織にとってどれだけ価値を届けているかに関心を持つ。そのために、チーム全員がステークホルダーのことを理解する必要がある。

目的を明確にするために、『このプロジェクトは〇〇のために存在している』『このプロダクトは□□のために存在している』という文書をステークホルダーと決める。

ステークホルダーが求めるものを作る

ステークホルダーの欲しいものを作るために、ステークホルダーとの距離を縮める。物理的に距離を縮めたり、プロダクトの目的をチームに浸透させるために部屋を片付けたり、プロダクト開発に巻き込んだりする。

ステークホルダーは何かしらをエンドユーザに提供する。よってステークホルダーが求めるものは、ユーザが欲しいもの。つまりユーザの課題を解決するようなものが求められる。そのために、ユーザサファリや、ゲリラテストが有効。

早く出荷する

うまくいっていないスクラムは、大抵スプリントごとにリリースできていない。

なぜ早くするのか?

ステークホルダーからのフィードバックを早期に得ずに、価値がありそうだと思う機能の開発はよくない。なぜなら、価値を決めるのはステークホルダーや市場だから。

フィードバックが遅れる要因としては、『最初から上手くやろう』という思いが強すぎることにある。

本当に価値があるかのフィードバックを受けてから、プロダクト戦略を調整しなければならないのに、それに出遅れる。

スクラムフレームワークの目的は、ステークホルダーに『完成』したインクリメントを十分な頻度で届けることであり、彼らに受け入れられるかわからないものにお金と時間を浪費しないようにすることである。

何をすると良いか

完成の定義を強化

『完成の定義』は、すべてのプロダクトバックログアイテムの実装を規定する一連のルール。 この定義を設定することで、チームの仕事にとって品質とプロ意識が何を意味するかの明確な定義をし、手戻りや品質問題を減らす。

パワフルクエスチョンを使う

開発チームがスプリントゴールに集中していられるように、パワフルクエスチョンを使う。

パワフルクエスチョンでは、スプリントゴールに向けて障害となっていることについて、デイリースクラムでチームに対して問いかけを行う。継続的に行うことで、チーム内で自主的な声掛けが生まれてくる。

フローを最適化する

スキルマトリックスでクロスファンクショナルを強化する。フローを最適化するために、スキルマトリックスを用いる。チームのスキルの棚卸しをし、何を改善するかをチームで決定する。

プロダクトバックログアイテムをスライスする

リリースが困難である理由の一つにアイテムが大きすぎることがある。 チームでリファインメントワークショップを開き、ワイズクラウドの方法で、スライスしていく。

スライスの目的は 不完全なものを届けるのではなく、それ自体で完全で高品質な大きなアイテムを、可能な限り小さくする実装の単位を見つける ことにある。

改善

どっかでかく

自己組織化

どっかでかく

参考

技術書の読み方

見つける

出会い方

まず、『何を読んだらいいかわからない』というのがあるけれど、出会い方としては次のようなものがある。

  1. 大型本屋での立ち読み
  2. 業界のインフルエンサのSNSTwitterやブログ等)
  3. Amazon のリコメンド
  4. これまでに読んだ本の参考書籍
  5. 著者、出版社、シリーズ
  6. 図書館

本の選び方

上記で出会った本に対して、次の観点で自分の欲しいものと一致しているかを確認する。

  • 目次や索引を読み、気になることが書かれているかを確認する
  • 誰を対象にしたものかを確認する。だいたい前書きに対象とする読者が書いてある

トレンドを知る

上記の方法で得られるのは、自身が知っていることベースのことが多く、先端的なことについては触れにくい状況にある。雑誌などを活用すると良い。

読み方

読書にかける時間

時間を無駄にしない読書の仕方について。

本の価値を時給換算する

それを読むのは『いま』なのかを考える。合わない本に時間をかけてもしょうがない。ちょっと読んで合わないと判断したら、流し読みに移行する。

合わないと判断する考え方に、サンクコスト がある。サンクコストは、すでに発生していて取り消しできない費用のことをいう。

例えば、1冊3000円の本を購入したとする。この時点でサンクコストは3000円となる。さらに、これを読み切るのに3時間くらいかかるとする。時給2000円の人にとっては本を読むのに、さらに6000円のコストが発生することになる。

サンクコストで、それだけの価値があるかを判断して、見合わないと判断したら、精読から流し読みに切り替えるのも手。

株の損切りと一緒で、出血を早く止めよう的な発想。

読書の目的をはっきりと持つ

読書を目的にしない。読書により、何を得たいかをはっきりさせながら読む。

手を動かす

技術書の場合は、実際に『手を動かす』という作業が必須。スポーツと同じで、自分の体を動かしてやってみないとできるようにはならない。

アウトプットは最大の成長

なぜか?

間違いに気づくためにもアウトプットは不可欠だから。発信しないと自身の理解が合ってるかを検証できない。

どうするか?

LTとかブログをやってみる。資料準備や書くことが頭の整理になる。 とりあえずやってみて好循環ループを作る。

参考

JavaScript:非同期処理

コールバック関数

非同期処理を学ぶ前に、まずはコールバック関数について復習。

コールバック関数とは

MDN Web Docsによると次のように定義されている。

コールバック関数とは、引数として他の関数に渡され、外側の関数の中で呼び出されて、何らかのルーチンやアクションを完了させる関数のこと

Callback function

コールバックには、すぐに実行される同期型コールバックと、非同期操作が完了した後に実行される非同期型コールバックがある。例えば次のコードがあるとする。

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;
  script.onload = () => callback(script);
  document.head.append(script);
}

loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
  alert(`Cool, the ${script.src} is loaded`);
  alert( _ ); // ロードされたスクリプトで宣言されている関数
});

上の例のコールバック関数は無名関数として、loadScript に渡され、loadScriptonload プロパティに対してイベントハンドラとして設定し、非同期処理を実現している。

image.png

コールバック地獄、破滅のピラミッド

コールバックはネストさせることができる。

image.png

上記のようなコードは、サイクロマティック複雑度(循環的複雑度)が高くなる。一般的に複雑度が高いほどメンテナンス性が悪くなるので、上記のような記述は避ける。

避けるために、次のように記述することもできる。

loadScript('1.js', step1);

function step1(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', step2);
  }
}

function step2(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('3.js', step3);
  }
}

function step3(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...すべてのスクリプトが読み込まれた後に続く (*)
  }
};

上記は関数間でジャンプがあるため読みにくく、また、関数名も再利用性が考慮されていない。ベストな方法は、promise を使う方法。

promise

イベントと処理を結びつける

MDN Web Docs によると、Promise オブジェクトは次のように定義される。

Promise オブジェクトは、非同期処理の完了 (もしくは失敗) の結果およびその結果の値を表します。 Promise は、作成された時点では分からなくてもよい値へのプロキシーです。非同期のアクションの成功値または失敗理由にハンドラーを結びつけることができます。

これにより、非同期メソッドは結果の値を返す代わりに、未来のある時点で値を提供する Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。

『作成された時点では分からなくてもよい値』というのはネットワーク経由でダウンロードするコードなどを指し、そのダウンロードの非同期処理の結果をイベントハンドラと結びつけるのが、Promise オブジェクトの役割。

非同期処理では結果を直ぐに返せない。その際、未来のある時点で値を返す Promise オブジェクトを返すようにすると、非同期処理でも同期処理と同様に値を返すことができるようになる。

// Promise オブジェクトのコンストラクタ構文
let promise = new Promise(function(resolve, reject) {
  // executor(処理)
});

上記構文の中で、非同期の処理自体は executor 部分で、resolve, reject メソッド部分は、JavaScript によって提供されるコールバックであり、次の意味を持つ。

  • resolve(value) – ジョブが正常に終了した場合。結果の value を持つ。
  • reject(error) – エラーが発生した場合。error はエラーオブジェクト。

new Promise で生成される Promise オブジェクトは状態を持っている。

  • 初期状態(pending):初期状態。成功でも失敗でもない
  • 履行(fulfilled):処理が成功
  • 拒否(rejected):処理が失敗。

ジョブが完了した時に resolve が呼ばれ、エラーした時に reject が呼ばれる。

image.png

例えば、次のようなコードを考える。

let promise = new Promise(function(resolve, reject) {
  // promise が作られたとき、関数は自動的に実行

  // 1秒後、ジョブが "done!" という結果と一緒に完了したことを合図する
  setTimeout(() => resolve("done!"), 1000);
});

promiseイベントハンドラ setTimeOut の完了時に、resolve"done!" を渡す。

イベントを受け取る

イベントの受け取りメソッドは、 .then, .catch, .finally がある。

.then が最も重要。.then でイベント発生時のデータを受け取る際の構文は次のようになっている。

// Promise 取得の基本構文
promise.then(
  function(result) { /* 成功した結果を扱う */ },
  function(error) { /* エラーを扱う */ }
);

上記で示した、1秒後のイベントハンドラの例に対して、Promise を受け取るコードは次のようになる。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(
  result => alert(result), // 1秒後に "done!" を表示
  error => alert(error) // 実行されない
);

拒否の場合は、次ようにする。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

promise.then(
  result => alert(result), // 実行されない
  error => alert(error) // 1秒後に "Error: Whoops!" を表示
);

正常完了だけでいい時は、 .then に1つだけ引数を渡す。

catch はエラーのみに関心がある時に使用し、finallytry{...}catch{...}finally 節のように Promise 完了時に必ず実行させたい時に使用する。finally はクリーンアップを実行するときに便利なハンドラ。

コールバックとの違い

違いを下記表に示す。

Promise コールバック
自然な順序でコード記述できる。どうするかは別で書ける 呼び出し時にどうするかを指定する
.thenを何度も呼べる 1つだけ

コールバックの場合、呼び出し時に何を実行させるかを指定する必要がある。

// コールバックを使った関数
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 ` + src));

  document.head.append(script);
}
// コールバックを使った関数を使う時
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
  alert(`Cool, the ${script.src} is loaded`);
  alert( _ ); // ロードされたスクリプトで宣言されている関数
});

Promise の場合、呼び出し時には Promise のコンストラクタだけでよく、イベント検知時の処理は別に記述することができる。

// Promiseを使った関数
function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error("Script load error: " + src));

    document.head.append(script);
  });
}

// Promiseで作った関数を使うとき
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);

promise.then(script => alert('Another handler...'));

Promiseチェーン

今一度、Promiseとコールバックの違いを確認する。

Promise コールバック
自然な順序でコード記述できる。どうするかは別で書ける 呼び出し時にどうするかを指定する
.thenを何度も呼べる 1つだけ

Promise チェーンは、.then を何度も呼び、順次実行される一連の非同期タスクを実現できる。例えば、次のような感じ。

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

実行の流れは、次のようになっている。

  1. 最初の promise は1秒で解決 (*)
  2. その後、.then ハンドラが呼ばれる (**)
  3. 返却された値は、次の .then ハンドラへ (***)
  4. …以下繰り返し

図で示すと、次の順序で実行されている。

image.png

これは、promise.then の戻り値が promise を返すため、続けて .then を呼び出すことができる。ハンドラが結果を返す時には、それは promise の結果となり、 .then はその結果とともに呼ばれる。

非同期版の Ruby のメソッドチェインみたいな感じと思っておけば良さそう🧐

単一の Promise に複数の .then をつけることもできるが、それはチェーンではないので注意。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

上記は、次のように解釈される。

image.png

.then の中にさらにハンドラがあるような場合、外側のハンドラは、その promise が完了するまで待ち、その結果を取得する。

image.png

Promise での例外対応

promise チェーンで例外が起きると、最も近い reject ハンドラに移る。例外対応の例を示す。

fetch('https://no-such-server.blabla') // rejects
  .then(response => response.json())
  .catch(err => alert(err))

C++ でも throw された例外は、catch されるまで関数呼び出しを辿っていくけど、それに近いようなものと考えれば良さそう🧐よって、 .catch で最も簡単な方法は、チェーンの末尾につけるということっぽい。

また、catch は明示的な reject だけでなく、throw や プログラムエラーも捕まえてくれる。

// 例外を投げる
new Promise(function(resolve, reject) {
  resolve("ok");
}).then(function(result) {
  throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!

// 存在しない関数を呼ぶ
new Promise(function(resolve, reject) {
  resolve("ok");
}).then(function(result) {
  blabla(); // このような関数はありません
}).catch(alert); // ReferenceError: blabla is not defined

再スロー

.catch の中から再度例外を投げることも可能。基本的な例外の動きは、C++と同じような感じだと思って良さそう。

// 実行: catch -> catch -> then
new Promise(function(resolve, reject) {

  throw new Error("Whoops!");

}).catch(function(error) { // (*)

  if (error instanceof URIError) {
    // エラー処理
  } else {
    alert("Can't handle such error");

    throw error; // ここで投げられたエラーは次の catch へジャンプします
  }

}).then(function() {
  /* 実行されません */
}).catch(error => { // (**)

  alert(`The unknown error has occurred: ${error}`);
  // 何も返しません => 実行は通常通りに進みます

});

reject を未処理にしない

例外発生時に、reject ハンドラがない場合、スクリプトはコンソールにエラーメッセージを表示して終了する。通常、ユーザはコンソールを見ないので、ユーザからすると『ただ落ちた』という状況になってしまうので、なんらかの対応が必要。

当該状況をユーザに通知するために、 unhandledrejection イベントハンドラが使える。unhandledrejection は、例外が発生し .catch がない場合に発生するハンドラで、エラー情報を持っており、なんらかの情報を伝えることに役立つ。

window.addEventListener('unhandledrejection', function(event) {
  // イベントオブジェクトは2つの特別なプロパティを持つ:
  alert(event.promise); // エラーを生成した promise
  alert(event.reason);  // 未処理のエラーオブジェクト
});

new Promise(function() {
  throw new Error("Whoops!");
}); // エラーを処理する catch がない

Promise API

Promise クラスには6つの静的メソッドがある。

Promise.all

Promise.all は、並列に複数の promise を実行し、すべて準備できるまで待ちたい時に使う。例えば、同時に複数のURLをダウンロードし、すべて完了したらコンテンツを処理する場合などがある。

構文は次のもの。

let promise = Promise.all(iterable);

通常、引数は promise の配列を取り、戻り値は新しい promise を返す。

次のように、配列として渡したり、

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert);

一般的には、処理データ配列を promise の配列にマップし、 Promise.all にラップすることが多いらしい。

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
];

// 各 url を promise の fetch(github url) へマップする
let requests = urls.map(url => fetch(url));

// Promise.all はすべてのジョブが解決されるまで待つ
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

Promise.all に渡された、いずれかの promisereject された場合 Promise.all から即座に reject が返される。reject された エラー内容は、Promise.all の全体の結果となる。

Promise.all では、いずれかの promisereject された段階で、以降の処理は無視される。

Promise.all は、all-or-nothing で、白か黒かといったケースに適当と言える。

Promise.allSettled

Promise.allSettled は結果に関わらず全ての promise が解決するまで待つ。1リクエストが失敗しても、他の結果が欲しいような時に使える。

結果の配列は以下を持つ。

  • 成功したレスポンスの場合: {status:"fulfilled", value:result}
  • エラーの場合: {status:"rejected", reason:error}

次のような感じで使える。

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => {
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });

Promise.allSettled は最近追加されたもので、古いブラウザでは対応していない。その場合、 Polyfill*1 してしまえばよく、下記のように、比較的簡単に導入できる。

if (!Promise.allSettled) {
  const rejectHandler = reason => ({ status: 'rejected', reason });

  const resolveHandler = value => ({ status: 'fulfilled', value });

  Promise.allSettled = function (promises) {
    const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
    return Promise.all(convertedPromises);
  };
}

Promise.race

Promise.all の最初の結果のみに注目したもの。構文は次のもの。

let promise = Promise.race(iterable);

例えば、次のコードの結果は、1 になる。最初の結果が Promise.race 全体の結果になる。最初以外の結果は無視される。

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1

Promise.any

Promise.all の最初にレスポンスが成功(fulfilled)した promise のみを持つ。promise 全てが reject された場合、Promise.any が返す promiseAggregateErrorreject を返す。

構文は次のもの。

let promise = Promise.any(iterable);

例えば、次のコードの結果は、2つ目の結果が最初にレスポンス成功するため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);

Promise.resolve/reject

最近のコードにおいては、async/await 構文によって、滅多に必要とされない。必要となった時に調べるくらいで良い。

Promisification

コールバックを受け付ける関数から Promise を返す関数への変換を行うのが Promisisication

よくわからないので、例で考える。

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 は指定された srcスクリプトを読み込み、エラーの場合は callback(err), 読み込みに成功した場合には callback(null, script) を呼び出す。

こいつを Promise を返すようにしてみる。src を渡し、戻り値で promise を返す。この promise は読み込みが成功すると、 script で resolve し、それ以外はエラーで reject する。

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(...)

上記の通り、loadScriptPromise は元の関数 loadScript のラッパーになっている。結果を Promise の resolve/reject に変換する独自のコールバックを提供し、呼び出す。

Promise に変換することの旨みみたいなことについてはピンときませんでした😅

Microtasks

Promise.then/ .catch/ .finally は常に非同期で、 Promise がすぐに解決されても、他の Promise 以外のコードが先に実行される。

理由は、非同期タスクは適切な管理が必要なため、実行はキューで管理されている。キューからの取り出しはタスクの実行が他に何も実行されていない時に開始される。

Promise の準備ができると、.then/ .catch/ .finally がキューに入れられる。

例えば次のコードの場合は、

let promise = Promise.resolve();

promise.then(() => alert("promise done!"));

alert("code finished");

code finished が先に実行される。理由は、.then は一度キューに入ってから、他に実行するタスクがなくなった時に実行されるため。つまり、Promise ハンドラは常に内部キューを通る。

この仕組みを知ると、unhandledrejection イベントがどう検知されるかがわかるようになる。 unhandledrejection は『microtask キューの最後で Promise エラーが処理されない場合に発生』となっている。

次の場合、キューには reject と catch が入るので、unhandledrejection は実行されない。

let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));

// 実行されません: error handled
window.addEventListener('unhandledrejection', event => alert(event.reason));

一方、次の場合はキューへの格納はイベント後(setTimeout)となるため、unhandledrejection 実行時にはキューが空のため、window.addEventListener が実行される。

let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')), 1000);

// 実行されます: Error: Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));

async/await

async は1つの単純なことを意味し、常に promise を返す関数。

例えば次のコードは、戻り値 1 を持つ resolve された promise を返す。

async function f() {
  return 1;
}

f().then(alert); // 1

async 関数の中でのみ使えるキーワードに await がある。awaitpromise が確定し、その結果を返すまで、JavaScript を待機させる。

例えば、次のコードを考える。

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // promise が解決するまで停止 (*)

  alert(result); // "done!"
}

f();

関数の実行は、(*) で一時停止し、 promise が確定した時に再開し、 result がその結果になる。上のコードの場合、1秒後に done が表示される。

await は文字通り、 promise が確定するまで JavaScript を待つ。待っている間、エンジンは他のジョブを実行することができ、CPUリソースを必要としない。promise.then よりも promise の結果を得るために読みやすく書きやすい構文になっている。

また、複数 promise を待つような場合には、Promise.all と相性がいい。次のように書け理解しやすい。

// 結果の配列をまつ
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

promisereject の場合はエラーをスローし、throw 文があるかのように振舞う。エラー自体は、 try-catch で処理できる。

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

参考

*1:ポリフィルとは、最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコード

JavaScript:ガベージコレクション

ガベージコレクションは、JavaScriptのメモリ管理の仕組みで、使用されなくなったオブジェクトを削除し、メモリを解放する。

実行は、ガベージコレクタと呼ばれる、バックグラウンドプロセスにより自動的に実行される。ガベージコレクタはオブジェクトを監視し、到達不可能になったオブジェクトを削除する。

C++とかだと、デストラクタやスマートポインタなどをプログラマが用いることで実現する管理を、JSではガベージコレクタが自動実行してくれる。素晴らしい🧐

到達性

JavaScriptのメモリ管理の主要コンセプトは到達性で、『到達可能な』値とは、何らかの形でアクセス可能、もしくは使用可能な値のことを指す。それらの値はガベージコレクタにより削除されず、メモリに格納されることが保証される。

具体的な例をいくつか示す。

  • 本質的に到達可能な値の基本セット(ルートと呼ばれる)
    • 実行中の関数のローカル変数とパラメータ
    • ネストされた呼び出しの関連する他関数のローカル変数とパラメータ
    • グローバル変数
  • 上記以外は、参照または参照チェーンにより、ルートから到達可能できるもの

    例えば、ローカル変数にオブジェクトAがあり、オブジェクトAがオブジェクトBへの参照を持っている場合、オブジェクトBは到達可能とみなされる。

到達性の例

  • シンプルなパターン

    例えば、次のようなオブジェクトがあるとする。

  let user = {
    name: "John"
  };

参照を図示すると次のような感じ。

image.png

ここで、user の値を上書きすると、オブジェクトへの参照がなくなり到達不可能になり、ガベージコレクタによりオブジェクトは破棄される。

  user = null;

image.png

  • 2つの参照がある場合

    例えば、次のようなオブジェクトがあるとする。

  let user = {
    name: "John"
  };

  let admin = user;

image.png

シンプルな例と同様に、user を上書きしても、オブジェクトへの参照は admin からのものが残っているためオブジェクトは削除されない。admin の値を上書きするとオブジェクトは削除される。

  • 連結されたオブジェクト

    次のようなオブジェクトを考える。

  function marry(man, woman) {
    woman.husband = man;
    man.wife = woman;

    return {
      father: man,
      mother: woman
    }
  }

  let family = marry({
    name: "John"
  }, {
    name: "Ann"
  });

image.png

上記の通り、すべてのオブジェクトはルート(<global variale>)から到達可能になっている。

2つの参照を削除してみる。

  delete family.father;
  delete family.mother.husband;

image.png

name: "Ann" のオブジェクトは到達可能だが、name: "John" のオブジェクトはルートから到達できるなくなるため、ガベージコレクタにより削除される。

  • 到達不可能な島

    連携されたオブジェクトの全体が到達不可能になり、メモリから削除される場合もある。例えば、上の family で示した例において、family の参照を削除してみる。

  family = null;

image.png

family から参照が消えたことで、ルートから参照できなくなり、島全体が到達不可能になったため削除されることになる。

ガベージのアルゴリズム

ガベージは、『マークアンドスイープ』と呼ばれる方法で行われる。具体的には次の手順が定期的に実行される。

  1. ガベージコレクタはルートを取得し、 それらを “マーク” (記憶)する
  2. 次に、そこからのすべての参照先を訪れ、マークする
  3. 次に、マークされたオブジェクトへアクセスし、それらの参照をマークする
  4. 上記を繰り返し、すべての到達可能な(ルートからの)参照が訪問されるまで続ける
  5. マークされたオブジェクトを除いた、すべてのオブジェクトが削除されます。

例えば、次のようなオブジェクトを考える。

image.png

右側のオブジェクトは、明らかに島全体が到達不可能な島になっている。ガベージコレクタがどう処理するかを追う。

  1. ルートをマークする

    image.png

  2. ルートの参照をマークする

    image.png

  3. それらの参照も辿れるところまでマークする

    image.png

  4. マークされていないオブジェクトを到達不可能と見做し、削除する

    image.png

参考

コミュニティ参加できない人に送る一言

この記事はフィヨルドブートキャンプ Part 1 Advent Calendar 2022 - Adventar 9日目の記事です。パート2もあります😁

昨日はharuna tsujitaさんの『自作サービス、ボツにしたサービス案たちをご紹介』という内容でした。色々なアイディアがあって、個人的に『面白そう👀』と思ったものもありましたが、しっかり想いを持ってボツにしていて、僕がWebサービス作る際にもそうした『想い』を持って判断していきたいと思いました💪

イントロダクション

こんにちは。goruchan ちゃんです。今日の内容は、先月のフィヨルドブートキャンプ (FBC) 内のビブリオバトル*1で発表させていただいた内容をベースに、『僕の行動を変えるきっかけ』について備忘録として残そうと思います。

コミュニティ参加ってハードルが高い

FBC に参加していたもののコミュニティ(ミートアップや輪読会、Discordチャット)への参加はしていませんでした。何となく『参加しにくい🫣』というハードルを感じていました。

ただ、キャンプ生の卒業式を視聴する(もちろん録画ですが、、、)と、卒業生のみんなが『もっと早くコミュニティに参加すればよかった』という言葉をおっしゃってました。

一念発起してミートアップに参加登録したこともありましたが、開始1時間前に『やっぱりムリー😫』となって参加キャンセルしたこともあります😅

そんな僕が、ビブリオバトルやアドベンドカレンダーといったコミュニティに、情報を発信する側として参加できるようになりました。

何がきっかけだったの?

きっかけとなったのは、『情熱プログラマー』を読んだことです。この本を読んだことで、次の2つを認識し『行動してみよう💪』となりました。

  • 自身の行動のエネルギーとは何か?
  • コミュニティ参加の障壁は何か?

素晴らしい本なので、本題に入る前に少し紹介させてください🤏

情熱プログラマーってどんな本?

前向きに成長を続けるためのノウハウを集めたエッセイのようなものです。『退屈しない人生を送りたい😤』と考えている方に、ぜひ読んでいただきたい内容になっていると思います。

著者について

この本は、著者 Chad Fowler 自身のキャリア選択時の失敗を教訓に書かれています。

Chad はソフトウェアエンジニアであり、著者であり、スピーカーであり、カンファレンスの主催者であり、そしてミュージシャンといういろんな肩書を持っています。

2011年のRubyist Magazine にも人物紹介されていたりする、偉大なソフトウェアエンジニアです。

本のスタイル

本の中では、『ミュージシャンで考えるとこうだ』という著者の経験を踏まえた比喩が多く用いられます。この比喩がとてもイメージしやすく分かりやすく、すっと腹落ちします。

僕がよくやっていたのは、彼の比喩の捉え方を、僕も自身に適用して、つまり自身の経験に紐づけて『部活の時とかは確かにそうだったな』と自問しながら読み進めていました。

この『自分ごととして捉えて読み進める』と自分自身の情熱と向き合いながら、どうすべきかということを深く考えながら読み進められるのではないかなと思います。

行動のエネルギーとは?

本題の1つ目は、行動のエネルギーについてです。

『情熱プログラマー』では行動のエネルギーは、情熱 だと言っています。こいつがあれば、自身の行動は劇的に前向きなものになります。

仕事なら、退屈せずにやりがいを持って取り組めるし、人生の大半は仕事なので、情熱を持って働ける仕事は退屈しない人生に繋がっていきます。

ただ、この『情熱』というものは厄介で、再生可能ではあるけれども、消費されていきます。自身の中の『情熱』を賢く利用していかないと、燃料切れをおこしてしまいます。

『情熱プログラマー』では、どうすれば情熱の燃料切れを起こさせないかについて教えてくれます。情熱を賢く運用することで、様々なことに対して前向きに、かつ持続的に行動できるようになります。

情熱の運用方法

『情熱プログラマー』では、5つの観点から情熱の運用方法を知ることができます。

それぞれ情熱に対してどう関係しているかについて、僕の認識をまとめました。製品という単語を、『自分自身』として捉えると分かりやすいです。

  • 市場選ぶ

    情熱をどこに置くかを考える。情熱を持って働くには、製品を取りまく環境を知らないといけない。というのも、製品は市場の中に存在するため闇雲に行動しても、それが市場が求めるものと違う場合、情熱を活かす先がなくなってしまう。活かす先が無いものは自然に消滅してしまう。

  • 製品に投資する

    情熱の根源にあるのは成長。人間は新しいことができるようになると達成感を感じ、さらに成長したいと感じるもの。効率的に成長を感じるには、ベターな自己投資方法を知る必要がある。成長を感じる機会が多くなればなるほど、情熱を増加することができる。

  • 実行に移す

    情熱を管理する。日々の業務で、情熱を持続可能なように管理して、情熱の燃料切れを起こさせないようにする。持続可能な運用とは、取り組む姿勢をよりポジティブなものに変えること。様々なことに対して、自分の意志で100%の力を出し切るという姿勢にする。そうすれば、これまでよりも、ずっと前向きな姿勢になり情熱を失いにくくなる。

  • マーケティング

    情熱をアピールする。情熱を持って働くことで製品の価値が上がっていく。ただ、それを評価してもらうことも情熱の獲得と維持には大事。そのためには、製品を知ってもらい、その価値をうまくアピールしないといけない。存在が知られていないものは、どんなに優れていても無いものに等しい。

  • 研鑽を怠らない

    情熱を持って働くためには、上述した ”市場を選ぶ〜マーケティング” のループを繰り返すのが大事。その中で注意が必要なのは、市場は常に流動的であるということ。持続的な情熱活動のためには、常に市場が求める需要を意識して研鑽を続ける必要がある。

コミュニティ参加の障壁は何か?

本題の2つ目です。これが一番自分に響いたことなのですが、『コミュニティ参加の障壁は恐怖心』ということです。

偉大な人も同じ

『情熱プログラマー:43 コネを作る』のなかで、著者がRuby界隈の人とコネクションするときのお話に次の文言があります。

本当に優れた人たちは、君が知り合いになりたがっているかどうかなんて気にしない。人は認められることが好きだし、また強い関心のある話題について語るのが好きだ。

僕らのような普通の人間と僕らの称賛する人たちの間にある最も大きな壁は、僕ら自身の恐れだ。

偉大な著者でさえ恐怖するということがとても響きました。同時に『自分だけじゃなく偉大な人も同じなんだ』と気づくことができました。

今までは、偉大な人たちはそうした感情など一切持たず、『好きに自由に簡単に』のような感じで何でもできてしまう特別な人たちだと思っていました。

しかし、それは誤解で偉大な人も自分と同じ感情を持っていたということは、とても自分の中で響き、『であれば、自分にもできるかも』という気持ちになれました。

正体が分かれば恐さも和らぐ

これまでのコミュニティ参加時の『やっぱムリー』となっていたことの真因は、自分自身の恐れなんだということに気づけました。正体不明なものへの恐さは『何をどうすればいいのか全くわからない』ため計り知れないですが、正体がわかるとその恐れへの対処も少しは考えられるようになりました。

僕自身にとっての障壁は、漫画やドラマとかでよくある『最大の敵は自分』というやつでした。今までは『それでも自分には無理だ😭』ってなってましたが、みんな同じ感情を持つということを今回学んだので、気が楽になり『とりあえずやってみるか』という気持ちになれました。

コミュニティ参加の恩恵

これまでのFBCの活動は一人作業しかなかったのですが、DISCORDのもくもく部屋などを活用できるようになりました。その中でキャンプ生と話をすることでモチベーションの維持がしやすく(情熱の維持)、また不明点の相談などもできるので、これまでよりも上手にFBCを進められるようになった気がします。

コミュニティへの参加は、同じ志を持つ人と関わることのできる機会なので、情熱の賢い運用において、特に効果のある方法だと感じました。いろんなことに参加し、情熱を吸収したり与えたりできるように、今後活動していきたいなと思いました。

変わりたいあなたへ送る一言

最後に、偉そうな章名ですが、今後心が折れそうになるかもしれない未来の自分に向けても、次の一言を送ります。

『恐いのはみんな一緒。楽に考えよう。だったら、とりあえずやってみてからでいいんじゃない?』

みんな恐怖心を持っているので、恐怖心を持つことを恥ずかしく思う必要もないです。気楽に考えて、とりあえずやってみちゃいましょう💪

明日は maimu さんです😄

*1:誰でも開催できる本の紹介コミュニケーションゲーム