下記のUdemyのコースが神なので、そのアウトプット第2段です。
【JS】初級者から中級者になるためのJavaScriptメカニズム | Udemy
書き方は下記が参考になります。
async関数においてtry/catchではなくawait/catchパターンを活用する - Qiita
2つの処理
同期処理
メインスレッドでコードが順番に実行されることです。
1つの処理が完了するまで次の処理は実行されません。
非同期処理
一時的にメインスレッドから処理が切り離されます。
裏側で処理を実行させつつ、他の処理を実行させること
スレッド
連続して実行される一本の処理の流れのことを指します。
- メインスレッド(シングルスレッド)
- サービスワーカー
- ウェブワーカー
メインスレッド
JS + レンダリング
FPS
1秒あたりの画面更新頻度のことを指します。
60fpsであれば、スムーズに動いている状態と言えます。
タスクキュー、コールスタック、ヒープ
JavaScriptの非同期処理をできる限り正確に理解する - Qiita
JavaScript の仕組み:メモリ管理+ 4つの共通のメモリリーク処理方法 - Qiita
全体像
コールスタック(Stack) → ヒープ → タスクキュー(Queue) → コールスタック(Stack)の順に実行されます。
サンプル実装
Udemyの実装を引っ張ってきました。
const btn = document.querySelector('button');
btn.addEventListener('click', function task2() {
console.log('task2 done');
});
function yo() {
setTimeout(function task1() {
console.log('task1 done');
}, 4000);
const startTime = new Date();
while (new Date() - startTime < 5000);
console.log('fn a done');
}
yo();
jsファイル実行後、すぐにボタンをクリックした場合
実施した操作
1. リロード
2. すぐにボタンクリック
処理の流れ
1. JSファイル実行
2. グローバルコンテキスト生成
3. 関数 yo
が実行されるので、5秒待機(コールスタックにあるグローバルコンテキストから)
4. また関数 yo
の中には非同期のsetTimeout
が実行される(これは4秒)
5. 3 の5秒待機している間に、ボタンをクリックされる
6. task2(クリックイベント)がタスクキューに登録される
7. 4で実行されたがsetTimeout
が終了し、関数task1
がタスクキューに追加される
8. 関数 yo
の実行が完了すると、コールスタック が空になる
9. イベントループが空になったコールスタック を検知し、タスクキューに知らせる
10. タスクキューは、コールスタック にクリックイベントを追加し、実行
11. 最後にタスクキューは、コールスタック に 関数task1
を追加し、実行
12. コールスタックが空になる
メインスレッドが占有されている = コールスタックにコンテキストが積まれている状態を指します。
実行結果
タスクキュー
実行待ちの非同期処理の実行順です。
実行順キューは、入れた順に取り出される性質を持っています。
そのため、タスクキューに入った順番で非同期処理は実行されることになります。
コールスタック
関数を格納しておくところです。
Call stack (コールスタック) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
イベントループ
コールスタックにコンテキストがあるかどうかを定期的に確認します。
存在しない場合には、タスクキューにコールスタックが空いている状態であることを通知する仕組みになります。
ヒープ
変数やオブジェクトなど諸々のデータが格納されます。
Promise
非同期処理の結果が格納されたオブジェクトです。
Promiseはどう動作するのか – Promiseを実装してみる | POSTD
then, catch, finally(全体)
// Promiseに書かれているfunctionは、callback関数
new Promise(function(resolve, reject) {
// 非同期で実行される
console.log('promise');
// rejectはPromiseのブロック内でしか使用できない
// reject("bye");
setTimeout(function() {
// resolveが呼ばれると、thenメソッドが呼ばれることになる
resolve("hello");
}, 1000);
}).then(function(data) { // data は resolve("hello")で私た "hello"が渡ってくる
// 非同期で実行される
// resolveが実行されないとthenメソッドは実行されない
console.log('then:' + data);
// then内でエラーをcatchしたい場合は、throwで記載する
throw new Error();
// 次のthenメソッドに値を渡したい時はreturn を書いておく
return data;
}).then(function(data) {
// 非同期で実行される
console.log('then:' + data);
return data;
}).catch(function(data) {
// 非同期で実行される
// rejectが呼ばれた時は、catchメソッドが呼ばれることになる
console.log('catch:' + data);
}).finally(function() {
// 非同期で実行される
// 最後に必ず実行される
// 引数を渡すことはできないので注意
console.log('finally');
})
[JavaScript] この先生きのこるための async/await 入門 - くろのて
Promiseの引数
Promiseでは、「resolve」と呼ばれる「正常データ」、「reject」と呼ばれる「エラー」が存在します。
resolveとrejectの取り出し方については、下記に詳細に書かれています。
私が async/await、promise をちゃんと理解するまでのステップ1,2,3 - Qiita
Promiseがもつ状態
- Fulfilled
- Rejected
- Pending
Promise と async/await の理解度をもう1段階上げる - Qiita
Promiseチェーン
非同期処理を連続で書いていくこと。
thenメソッド内で、Promiseチェーンをつなぐ時はPromiseのインスタンスを返す必要があります。
function sleep(val) {
// 2番目にthenメソッドを使用するつために、Promiseオブジェクトをreturnする
return new Promise(function(resolve) {
setTimeout(function() {
console.log(val++);
resolve(val);
}, 1000);
});
}
sleep(0).then(function(val) {
// 次にthenを使用するためにPromiseのオブジェクトを返す。
// Promiseのオブジェクトを返さない場合は、非同期処理を待たずに次のメソッドが実行されてしまう
return sleep(val);
}).then(function(val) {
return sleep(val);
})
Promiseの並列処理
Promise.all()
全て実行した後に次の関数が呼ばれます。
Promise.race()
どれか非同期処理一つ完了した時に実行されます。
Promise.all([sleep(2), sleep(3), sleep(4)])
.then(function (data) {
console.log(data);
}).catch(function(e) {
console.error(e);
});
Await / Async
Promiseを直感的に記述できるようにしたもの
Async
Promiseを返却する関数の宣言を行います。
必ず Promiseを返します。
Await
Promiseを返却する関数の非同期処理が完了するまで待ちます。
awaitの結果に、resolve()
に渡された引数を返します。
また当然ですが、 await
は async
の中でしか宣言できないので、注意しましょう。
async function init() {
// awaitを使用する際は、必ず asyncを使用する
let val = await sleep(0);
val = await sleep(val);
return val
}
// functionsの引数として、init()でreturnしたvalが入ることになる
init().then(function(val) {
console.log('hello' + val)
});
fetch
fetch(リクエスト先のURL)
で実施可能
async function fetchUsers() {
const response = await fetch('users.json');
const json = await response.json();
}
fetchUsers();
例外処理
try/catch/finally
で実装することができます。
async function execute() {
try {
const users = await fetchUsers();
// データがない場合を想定
for(const user of users) {
console.log(`I'm ${user.name}, ${user.age} years old`)
}
} catch(e) {
// エラーメッセージごとに分岐したい場合は下記のような実装になる
if(e instanceof NoDataError) {
console.error(e);
}
} finally {
console.log('bye');
}
console.log('end');
}
execute();
カスタムエラー
// Errorを拡張してあげて、実装すれば良い
// このときエラーの名前は必ずつけるようにする
class NoDataError extends Error {
constructor(message) {
super(message);
this.name = 'NoDataError';
}
}