Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Promise と async/await

Jeongmin LEE
October 16, 2024
14

Promise と async/await

Jeongmin LEE

October 16, 2024
Tweet

Transcript

  1. let g = 0; setTimeout(() => { g = 100

    }, 0) console.log(g) Q. console.log には何が出力されるでしょうか?
  2. console.log は setTimeout の内部処理がわからない let g = 0; setTimeout(() =>

    { g = 100 }, 0) console.log(g) Q. console.log には何が出力されるでしょうか? 正解:0
  3. Memory Heap Call Stack Web APIs Task Queue Event Loop

    let g = 0; (されて CallStack から消える) ①実行 let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) ①実行 なぜわからないのか
  4. Memory Heap Call Stack Web APIs Task Queue Event Loop

    setTimeout(() => { g = 100 }, 0) let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか ②実行 ②実行
  5. Memory Heap Call Stack Web APIs Task Queue Event Loop

    setTimeout(() => { g = 100 }, 0) let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか ②実行 setTimeout(() => { g = 100 }, 0) こっちに移動する
  6. Memory Heap Call Stack Web APIs Task Queue Event Loop

    let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか ②実行 setTimeout(() => { g = 100 }, 0) setTimeout が実行されたとみなされ、 Call Stack からは消える
  7. Memory Heap Call Stack Web APIs Task Queue Event Loop

    let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか () => { g = 100 } コールバック関数を Task Queue に送る
  8. Memory Heap Call Stack Web APIs Task Queue Event Loop

    let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか console.log(g) ③実行 (この時点の g の値は 0) ③実行 () => { g = 100 } (されて CallStack から消える)
  9. Memory Heap Call Stack Web APIs Task Queue Event Loop

    let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか () => { g = 100 } ④実行 ④実行 非同期処理のコールバック関数は 同期処理が終わった後に実行される
  10. const get = (url, callback) => { const xhr =

    new XMLHttpRequest(); xhr.open('GET', url); xhr.send(); xhr.onload = () => { if (xhr.status === 200) callback(JSON.parse(xhr.response)); else console.error(xhr.statusText); }; }; 1. Callback Hell による可読性の低下 データを取得する非同期関数を作ってみよう
  11. ネストし始めたら地獄 (Callback Hell) a の結果で b を GET して、その結果でまたcをGETして、その結果でまた(以下略) get(‘/step1’,

    (a) => { get(`/step2/${a}`, (b) => { get(`/step3/${b}`, (c) => { get(`/step4/${c}`, (d) => { console.log(c) }) }) }) })
  12. 2. エラー処理が難しい try { throw new Error("Error!"); } catch (error)

    { console.error("エラー", error) } try-catch エラーを発生させてみよう
  13. try { throw new Error("Error!"); } catch (error) { console.error("エラー",

    error) } try-catch エラーを発生させてみよう エラーをキャッチしてる 2. エラー処理が難しい
  14. try { setTimeout(() => { throw new Error("Error!") }, 0)

    } catch (error) { console.error("エラー", error) } try-catch エラーを発生させてみよう 2. エラー処理が難しい
  15. try { setTimeout(() => { throw new Error("Error!") }, 0)

    } catch (error) { console.error("エラー", error) } try-catch エラーを発生させてみよう エラーがキャッチできない 2. エラー処理が難しい
  16. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか try-catch ブロックが コールスタックに入る
  17. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか setTimeout(() => {  throw new Error("Error!") }, 0) setTimeout は こっちに移動する
  18. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { } catch (error) { console.error("エラー", error) } try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか setTimeout(() => {  throw new Error("Error!") }, 0) setTimeout が実行されたとみなされ、 Call Stack からは消える
  19. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { } catch (error) { console.error("エラー", error) } try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか () => {  throw new Error("Error!") } コールバック関数を Task Queue に移動、 コールスタックが空になるのを待つ
  20. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { } catch (error) { console.error("エラー", error) } try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか 同期処理である try–catch ブロックの 残り処理が実行される。この時点では エラーがないので、正常終了する () => {  throw new Error("Error!") }
  21. Memory Heap Call Stack Web APIs Task Queue Event Loop

    try { setTimeout(() => {   throw new Error("Error!")  }, 0) } catch (error) { console.error("エラー", error) } なぜエラーがキャッチできないのか キューで待ってたコールバック関数が エラーを発生してるが、もう try-catch ブロックは終了した後なの でエラーキャッチができない () => {  throw new Error("Error!") }
  22. 補足) settled になったら状態は変化しない const promise = new Promise((resolve, reject) =>

    { setTimeout(() => { resolve(); // すでにresolveされているため無視される reject(new Error("エラー")); }, 16); }); promise.then(() => { console.log("Fulfilledとなった"); }, (error) => { // この行は呼び出されない }); 一度でも Settled となった Promise インスタンスは、それ以降別の状態には変化しない
  23. 補足) settled になったら状態は変化しない const promise = new Promise((resolve, reject) =>

    { setTimeout(() => { resolve(); // すでにresolveされているため無視される reject(new Error("エラー")); }, 16); }); promise.then(() => { console.log("Fulfilledとなった"); }, (error) => { // この行は呼び出されない }); 一度でも Settled となった Promise インスタンスは、それ以降別の状態には変化しない 実際実行してみた
  24. Promise インスタンス - Promise はクラスなので new 演算子で Promise のインスタンスを作成し、利用する -

    コンストラクタには resolve 関数と reject 関数の2つの引数を取る関数(executor)を渡す const promise = new Promise((resolve, reject) => { // 非同期の処理が成功したときはresolveを呼ぶ // 非同期の処理が失敗したときはrejectを呼ぶ });
  25. Promise インスタンスには状態変化をした際に呼び出されるコールバック関数を登録できる fulfilled or rejected の2択 (Promise 生成直後の基本状態は pending のため)

    = 非同期処理が成功したときと、失敗したときのコールバック関数が登録できる Promise で非同期処理を扱う
  26. const promise = () => { return new Promise((resolve, reject)

    => { const success = Math.random() > 0.5; // 50%の確率で成功 setTimeout(() => { if (success) resolve("成功!"); // 非同期処理成功 else reject("失敗!"); // 非同期処理失敗 }, 2000); }); } 非同期処理をする Promise インスタンスを返す関数を作成してみよう
  27. promise() .then((result) => { console.log(result); // 非同期処理成功 }).catch((error) => {

    console.error(error); // 非同期処理失敗 }); 非同期処理の結果を扱う then や catch メソッドで成功時や失敗時に呼び出される処理をコールバック関数として登録する
  28. status pending result undefined status “fulfilled” result value status “rejected”

    result error resolve(value) reject(error) Promise の状態と結果値
  29. get(‘/step1’, (a) => { get(`/step2/${a}`, (b) => { get(`/step3/${b}`, (c)

    => { get(`/step4/${c}`, (d) => { console.log(c) }) }) }) }) Promise のメリット(1) - Callback Hell の解消 get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) => get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`)) .then((d) => { console.log(d); }) Callback Promise
  30. get(‘/step1’, (a) => { get(`/step2/${a}`, (b) => { get(`/step3/${b}`, (c)

    => { get(`/step4/${c}`, (d) => { console.log(c) }) }) }) }) Promise のメリット(1) - Callback Hell の解消 Callback Promise get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) => get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`)) .then((d) => { console.log(d); }) Promise の後続処理メソッドはいつも Promise インスタンスを返すので、連続的に 呼び出すことができる。これをPromise チェーン (promise chaining)と呼ぶ。
  31. Promise のメリット(2) - エラーハンドリング Callback Promise try { setTimeout(() =>

    { throw new Error("Error!") }, 0) } catch (error) { console.error("エラー", error) } new Promise((_, reject) => { setTimeout(() => { reject(new Error("Error!")) }, 0) }).catch(error => { console.error("エラー", error) })
  32. Promise のメリット(2) - エラーハンドリング Callback Promise try { setTimeout(() =>

    { throw new Error("Error!") }, 0) } catch (error) { console.error("エラー", error) } new Promise((_, reject) => { setTimeout(() => { reject(new Error("Error!")) }, 0) }).catch(error => { console.error("エラー", error) }) エラーがキャッチできる
  33. Promise の限界(1) - あいかわらず複雑 get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) =>

    get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`)) .then((d) => { console.log(d); }) Callback Hell よりは改善されたけど、 非同期処理が複数あって Promise チェーンが 長くなったらやはり可読性が落ちる
  34. Promise の限界(2) - エラーハンドリングも複雑になりえる get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) =>

    get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`)) .then((d) => { console.log(d); }) このコードでエンドポイントごとにエラーハンドリングをしたら?
  35. Promise の限界(2) - エラーハンドリングも複雑になりえる get('/step1') .then(a => get(`/step2/${a}`)) .catch(error =>

    console.error(`Step 1 failed: ${error}`)) .then(b => get(`/step3/${b}`)) .catch(error => console.error(`Step 2 failed: ${error}`)) .then(c => get(`/step4/${c}`)) .catch(error => console.error(`Step 3 failed: ${error}`)) .then(d => console.log(d)) .catch(error => console.error(`Step 4 failed: ${error}`)); (なにがなんだか、、)
  36. async function doAsync() { return "値"; } doAsync().then(value => {

    console.log(value); // => "値" }); Async Function は必ずPromiseインスタンスを返す Async Function は関数の定義に async キーワードをつけることで定義できる Async Function *Typescript の型推論でも Promise 型になる
  37. async function doAsync() { return "値"; } doAsync().then(value => {

    console.log(value); // => "値" }); Async Function は必ずPromiseインスタンスを返す function doAsync() { return Promise.resolve("値"); } doAsync().then(value => { console.log(value); // => "値" }); Async Function 通常の Function Async Functionは Promise の上に作られた構文 *返り値をラップしたPromiseインスタンスを返す この2つのコードは同じことをやってる
  38. const promise = () => { return new Promise((resolve, reject)

    => { const success = Math.random() > 0.5; // 50%の確率で成功 setTimeout(() => { if (success) resolve("成功!"); // 非同期処理成功 else reject("失敗!"); // 非同期処理失敗 }, 2000); }); } Async Function で非同期処理を扱ってみよう 非同期処理をするために Promise インスタンスを返す関数を作成(ここまでは先と同じ)
  39. promise() .then((result) => { console.log(result); // 非同期処理成功 }).catch((error) => {

    console.error(error); // 非同期処理失敗 }); const handlePromise = async () => { try { const result = await promise(); console.log(result); // 非同期処理成功 } catch (error) { console.error(error); // 非同期処理失敗 } } 非同期処理の結果を扱う Promiseチェーン Async Function
  40. promise() .then((result) => { console.log(result); // 非同期処理成功 }).catch((error) => {

    console.error(error); // 非同期処理失敗 }); const handlePromise = async () => { try { const result = await promise(); console.log(result); // 非同期処理成功 } catch (error) { console.error(error); // 非同期処理失敗 } } 非同期処理の結果を扱う Promiseチェーン Async Function Async Function内では await 式が利用できる
  41. await 式 - Promise の非同期処理が完了するまで待つ構文 - Promiseインスタンスの状態が変わると、次の行の処理を再開 async function doAsync()

    { // 非同期処理 } async function asyncMain() { // doAsyncの非同期処理が完了するまでまつ await doAsync(); // 次の行はdoAsyncの非同期処理が完了されるまで実行されない console.log("この行は非同期処理が完了後に実行される"); }
  42. await 式 - Promise の非同期処理が完了するまで待つ構文 - Promiseインスタンスの状態が変わると、次の行の処理を再開 async function doAsync()

    { // 非同期処理 } async function asyncMain() { // doAsyncの非同期処理が完了するまでまつ await doAsync(); // 次の行はdoAsyncの非同期処理が完了されるまで実行されない console.log("この行は非同期処理が完了後に実行される"); } - fulfilled の場合、resolve された値が await 式の返り値となる - rejected の場合、その場でエラーを throw する
  43. get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) => get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`))

    .then((d) => { console.log(d); }) Promise async function fetchData() { const a = await get('/step1'); const b = await get(`/step2/${a}`); const c = await get(`/step3/${b}`); const d = await get(`/step4/${c}`); } fetchData(); async/await のメリット(1) - 非同期を同期のように Async Function
  44. get('/step1') .then(a => get(`/step2/${a}`)) .catch(error => console.error(`Step 1 failed: ${error}`))

    .then(b => get(`/step3/${b}`)) .catch(error => console.error(`Step 2 failed: ${error}`)) .then(c => get(`/step4/${c}`)) .catch(error => console.error(`Step 3 failed: ${error}`)) .then(d => console.log(d)) .catch(error => console.error(`Step 4 failed: ${error}`)); async/await のメリット(2) - エラーハンドリングが分かりやすく 先のなにがなんだかコード
  45. async function fetchData() { const a = await fetchStep1(); if

    (!a) return; // エラーが起きたら終了 const b = await fetchStep2(); if (!b) return; // エラーが起きたら終了 const c = await fetchStep3(); if (!c) return; // エラーが起きたら終了 const d = await fetchStep4(); if (d) console.log(d); } fetchData(); async function fetchStep1() { try { return await get('/step1'); } catch (error) { console.error(`Step 1 failed: ${error}`); return null; // エラー発生時は null 返す } } async/await のメリット(2) - エラーハンドリングが分かりやすく こんな感じで各ステップを関数で切り出して、、 順番通り呼び出す
  46. コールバック関数 - Callback Hell、エラーキャッチが難しいという限界がある Promise - ES6(2015年)で投入された、非同期処理の状態や結果を表現するビルトインオブジェクト - Promise チェーンを使って

    Callback Hell を解決できるし、エラーもキャッチできる - Promise チェーンが長くなったら複雑になるし、部分的なエラーハンドリングも複雑になりえる async/await - EES8(2017年)で投入された、非同期処理を行う関数を定義する構文 - Async FunctionはPromiseの上に作られた構文であり、必ず Promise インスタンスを返す - 非同期処理を同期処理のように使えるし、エラーハンドリングも分かりやすくなる
  47. - 이웅모, 『모던 자바스크립트 Deep Dive : 자바스크립트의 기본 개념과

    동작 원리』, 위키북스 , 2020 - JavaScript Primer, 「[ES2015] Promise」, https://jsprimer.net/basic/async/#promise, (2024.10.14) - JavaScript Primer, 「[ES2017] Async Function」,https://jsprimer.net/basic/async/#async-function , (2024.10.15) 参考資料