LoginSignup
1

More than 3 years have passed since last update.

【Javascript】JSを根本から再復習、非同期処理(async/await)とか

Last updated at Posted at 2020-05-16

下記の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)の順に実行されます。

image.png
並行モデルとイベントループ - JavaScript | MDN より引用

サンプル実装

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. コールスタックが空になる

メインスレッドが占有されている = コールスタックにコンテキストが積まれている状態を指します。

実行結果

image.png

タスクキュー

実行待ちの非同期処理の実行順です。
実行順キューは、入れた順に取り出される性質を持っています。
そのため、タスクキューに入った順番で非同期処理は実行されることになります。

コールスタック

関数を格納しておくところです。

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() に渡された引数を返します。
また当然ですが、 awaitasyncの中でしか宣言できないので、注意しましょう。

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';
  }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1