Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Promise と async/await
Search
Jeongmin LEE
October 16, 2024
0
35
Promise と async/await
Jeongmin LEE
October 16, 2024
Tweet
Share
More Decks by Jeongmin LEE
See All by Jeongmin LEE
Webレンダリング技術の進化と課題
gardensky511
0
38
シングルな Javascript の非同期処理
gardensky511
0
120
集合で理解する Typescript
gardensky511
1
170
Javascript のデータ型 プリミティブ型・オブジェクト
gardensky511
0
130
Featured
See All Featured
How STYLIGHT went responsive
nonsquared
98
5.4k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
27
1.9k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
Scaling GitHub
holman
459
140k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Building an army of robots
kneath
303
45k
Done Done
chrislema
182
16k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
100
18k
Making Projects Easy
brettharned
116
6k
Six Lessons from altMBA
skipperchong
27
3.6k
The Cost Of JavaScript in 2023
addyosmani
47
7.3k
Transcript
Promise と async/await Javascript 非同期処理完全理解(2)
アイスブレイクでまずみなさんに質問をします
この2つのコードで型が違う理由ご存知ですか? (知ってる人はネタバレ禁止です笑)
わからなくても大丈夫
この勉強会で完全理解できる!
職業 自己紹介 韓国人です。大学時代にはげしくアニメ オタクをやってて日本語が上手くなりま した。 フロントエンドエンジニア(2020/11 ~) React、Typescript メインでやってます
CONTENTS 1. Callback 関数の限界 2. Promise 3. async/await 4. まとめ
1. Callback 関数の限界
let g = 0; setTimeout(() => { g = 100
}, 0) console.log(g) Q. console.log には何が出力されるでしょうか?
console.log は setTimeout の内部処理がわからない let g = 0; setTimeout(() =>
{ g = 100 }, 0) console.log(g) Q. console.log には何が出力されるでしょうか? 正解:0
Memory Heap Call Stack Web APIs Task Queue Event Loop
let g = 0; (されて CallStack から消える) ①実行 let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) ①実行 なぜわからないのか
Memory Heap Call Stack Web APIs Task Queue Event Loop
setTimeout(() => { g = 100 }, 0) let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか ②実行 ②実行
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) こっちに移動する
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 からは消える
Memory Heap Call Stack Web APIs Task Queue Event Loop
let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか () => { g = 100 } コールバック関数を Task Queue に送る
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 から消える)
Memory Heap Call Stack Web APIs Task Queue Event Loop
let g = 0; setTimeout(() => { g = 100 }, 0) console.log(g) なぜわからないのか () => { g = 100 } ④実行 ④実行 非同期処理のコールバック関数は 同期処理が終わった後に実行される
非同期処理結果に対する後続処理は、コールバック関数で行う必要がある let g = 0; setTimeout(() => { g =
100; console.log(g); // 100 }, 0)
でもコールバック関数だけでは限界があり、、
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 による可読性の低下 データを取得する非同期関数を作ってみよう
get('なにかのurl', (a) => { console.log(a) }) 使うときは url と callback
関数を渡す
ネストし始めたら地獄 (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) }) }) }) })
Image From wikipedia https://www.linkedin.com/pulse/callback-hell-javascript-abir-tasrif-anto/ 可読性が落ちてミスが多発する
2. エラー処理が難しい try { throw new Error("Error!"); } catch (error)
{ console.error("エラー", error) } try-catch エラーを発生させてみよう
try { throw new Error("Error!"); } catch (error) { console.error("エラー",
error) } try-catch エラーを発生させてみよう エラーをキャッチしてる 2. エラー処理が難しい
try { setTimeout(() => { throw new Error("Error!") }, 0)
} catch (error) { console.error("エラー", error) } try-catch エラーを発生させてみよう 2. エラー処理が難しい
try { setTimeout(() => { throw new Error("Error!") }, 0)
} catch (error) { console.error("エラー", error) } try-catch エラーを発生させてみよう エラーがキャッチできない 2. エラー処理が難しい
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 ブロックが コールスタックに入る
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 は こっちに移動する
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 からは消える
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 に移動、 コールスタックが空になるのを待つ
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!") }
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!") }
この限界を克服するために登場したものが、、
3. Promise
Promise とは - ES6(2015年)で投入された、非同期処理の状態や結果を表現するビルトインオブジェクト - 非同期作業をより簡単に処理できる
Promise とは - ES6(2015年)で投入された、非同期処理の状態や結果を表現するビルトインオブジェクト - 非同期作業をより簡単に処理できる
非同期処理の状態 pending fulfilled rejected 非同期処理がまだ行われてない状態 非同期処理が完了した状態(成功) 非同期処理が完了した状態(失敗) 非同期処理には3つの状態が存在する
非同期処理の状態 pending fulfilled rejected 非同期処理がまだ行われてない状態 非同期処理が完了した状態(成功) 非同期処理が完了した状態(失敗) 非同期処理には3つの状態が存在する settled (pending
ではない状態)
補足) settled になったら状態は変化しない const promise = new Promise((resolve, reject) =>
{ setTimeout(() => { resolve(); // すでにresolveされているため無視される reject(new Error("エラー")); }, 16); }); promise.then(() => { console.log("Fulfilledとなった"); }, (error) => { // この行は呼び出されない }); 一度でも Settled となった Promise インスタンスは、それ以降別の状態には変化しない
補足) settled になったら状態は変化しない const promise = new Promise((resolve, reject) =>
{ setTimeout(() => { resolve(); // すでにresolveされているため無視される reject(new Error("エラー")); }, 16); }); promise.then(() => { console.log("Fulfilledとなった"); }, (error) => { // この行は呼び出されない }); 一度でも Settled となった Promise インスタンスは、それ以降別の状態には変化しない 実際実行してみた
Promise インスタンス - Promise はクラスなので new 演算子で Promise のインスタンスを作成し、利用する -
コンストラクタには resolve 関数と reject 関数の2つの引数を取る関数(executor)を渡す const promise = new Promise((resolve, reject) => { // 非同期の処理が成功したときはresolveを呼ぶ // 非同期の処理が失敗したときはrejectを呼ぶ });
Promise インスタンスには状態変化をした際に呼び出されるコールバック関数を登録できる Promise で非同期処理を扱う
Promise インスタンスには状態変化をした際に呼び出されるコールバック関数を登録できる fulfilled or rejected の2択 (Promise 生成直後の基本状態は pending のため)
= 非同期処理が成功したときと、失敗したときのコールバック関数が登録できる Promise で非同期処理を扱う
const promise = () => { return new Promise((resolve, reject)
=> { const success = Math.random() > 0.5; // 50%の確率で成功 setTimeout(() => { if (success) resolve("成功!"); // 非同期処理成功 else reject("失敗!"); // 非同期処理失敗 }, 2000); }); } 非同期処理をする Promise インスタンスを返す関数を作成してみよう
promise() .then((result) => { console.log(result); // 非同期処理成功 }).catch((error) => {
console.error(error); // 非同期処理失敗 }); 非同期処理の結果を扱う then や catch メソッドで成功時や失敗時に呼び出される処理をコールバック関数として登録する
status pending result undefined status “fulfilled” result value status “rejected”
result error resolve(value) reject(error) Promise の状態と結果値
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
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)と呼ぶ。
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) })
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) }) エラーがキャッチできる
でも Promise にも限界はある
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 チェーンが 長くなったらやはり可読性が落ちる
Promise の限界(2) - エラーハンドリングも複雑になりえる get('/step1') .then((a) => get(`/step2/${a}`)) .then((b) =>
get(`/step3/${b}`)) .then((c) => get(`/step4/${c}`)) .then((d) => { console.log(d); }) このコードでエンドポイントごとにエラーハンドリングをしたら?
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}`)); (なにがなんだか、、)
この限界を克服するために登場したものが、、
3. async/await
async/await とは - ES8(2017年)で投入された、非同期処理を行う関数を定義する構文 - Async Function は通常の関数とは異なり、必ずPromiseインスタンスを返す
async function doAsync() { return "値"; } doAsync().then(value => {
console.log(value); // => "値" }); Async Function は必ずPromiseインスタンスを返す Async Function は関数の定義に async キーワードをつけることで定義できる Async Function *Typescript の型推論でも Promise 型になる
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つのコードは同じことをやってる
const promise = () => { return new Promise((resolve, reject)
=> { const success = Math.random() > 0.5; // 50%の確率で成功 setTimeout(() => { if (success) resolve("成功!"); // 非同期処理成功 else reject("失敗!"); // 非同期処理失敗 }, 2000); }); } Async Function で非同期処理を扱ってみよう 非同期処理をするために Promise インスタンスを返す関数を作成(ここまでは先と同じ)
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
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 式が利用できる
await 式 - Promise の非同期処理が完了するまで待つ構文 - Promiseインスタンスの状態が変わると、次の行の処理を再開 async function doAsync()
{ // 非同期処理 } async function asyncMain() { // doAsyncの非同期処理が完了するまでまつ await doAsync(); // 次の行はdoAsyncの非同期処理が完了されるまで実行されない console.log("この行は非同期処理が完了後に実行される"); }
await 式 - Promise の非同期処理が完了するまで待つ構文 - Promiseインスタンスの状態が変わると、次の行の処理を再開 async function doAsync()
{ // 非同期処理 } async function asyncMain() { // doAsyncの非同期処理が完了するまでまつ await doAsync(); // 次の行はdoAsyncの非同期処理が完了されるまで実行されない console.log("この行は非同期処理が完了後に実行される"); } - fulfilled の場合、resolve された値が await 式の返り値となる - rejected の場合、その場でエラーを throw する
最初の質問の正解大公開 await で非同期処理の完了を待つので、resolve され た値である string が推論される await がない=非同期処理の完了を待たないので、 Promise<string>
型が推論される
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
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) - エラーハンドリングが分かりやすく 先のなにがなんだかコード
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) - エラーハンドリングが分かりやすく こんな感じで各ステップを関数で切り出して、、 順番通り呼び出す
4. まとめ
コールバック関数 - Callback Hell、エラーキャッチが難しいという限界がある Promise - ES6(2015年)で投入された、非同期処理の状態や結果を表現するビルトインオブジェクト - Promise チェーンを使って
Callback Hell を解決できるし、エラーもキャッチできる - Promise チェーンが長くなったら複雑になるし、部分的なエラーハンドリングも複雑になりえる async/await - EES8(2017年)で投入された、非同期処理を行う関数を定義する構文 - Async FunctionはPromiseの上に作られた構文であり、必ず Promise インスタンスを返す - 非同期処理を同期処理のように使えるし、エラーハンドリングも分かりやすくなる
ご清聴ありがとうございました
- 이웅모, 『모던 자바스크립트 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) 参考資料