概要
「タイムリープTypeScript 〜TypeScript始めたてのあの頃に知っておきたかったこと〜」
8日目の記事です。
今回はあの頃の私がつまづいたPromise・async/awaitの使い方を説明して行きます!
「内容がJsやん」というご指摘があるかも知れません。
私自身が型安全な非同期処理を作る上で、押さえておきたかったポイント・基本的な考え方を記事にすることで,これから触り始める方々の一助になるのではないかと考えています。
2年目の新米ですが、どうぞ暖かい目で読んでいただけますと幸いです。
Chapter1:同期処理・非同期処理 is 何?
同期処理(sync)
コードを順番に処理していき、ひとつの処理が終わるまで次の処理は行いません。
非同期処理(async)
コードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を評価します。
実際のサンプルコードは以下のドキュメントが分かりやすくまとめていただいています。
併せてご確認ください。
同期処理・非同期処理のざっくりした概要を簡単に理解したところで早速次のChapterではPromise
について説明します。
Chapter2:Promise is 何?
PromiseはES2015で導入された非同期処理の結果を表現するビルトインオブジェクトです。
エラーファーストコールバックは非同期処理を扱うコールバック関数の最初の引数にエラーオブジェクトを渡すというルールでした。 Promiseはこれを発展させたもので、単なるルールではなくオブジェクトという形にして非同期処理を統一的なインターフェースで扱うことを目的にしています。
非同期処理を見通しよく書くことができるようになりました
Promiseを使わない場合はどうなるの?
サンプルコード
type Callback<T> = (result: T) => void;
// 非同期でAPIにリクエストを投げて値を取得する処理
function request1(callback: Callback<number>) {
setTimeout(() => {
callback(1);
}, 1000);
}
// 受け取った値を別のAPIにリクエストを投げて値を取得する処理
function request2(result1: number, callback: Callback<number>) {
setTimeout(() => {
callback(result1 + 1);
}, 1000);
}
// 受け取った値を別のAPIにリクエストを投げて値を取得する処理
function request3(result2: number, callback: Callback<number>) {
setTimeout(() => {
callback(result2 + 2);
}, 1000);
}
// コールバック地獄
// 一つ前のAPIの結果を待って次のAPIをリクエストするために
// コールバック関数が入れ子になってしまう
request1((result1) => {
request2(result1, (result2) => {
request3(result2, (result3) => {
console.log(result3); // -> 4
});
});
});
コールバック関数が入れ子になってしまうので可読性が低下してしまいます
request1((result1) => {
request2(result1, (result2) => {
request3(result2, (result3) => {
console.log(result3); // -> 4
});
});
});
Promiseを使う場合はどうなるの?
サンプルコード
// 非同期でAPIにリクエストを投げて値を取得する処理
function request1(): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
// 受け取った値を別のAPIにリクエストを投げて値を取得する処理
function request2(result1: number): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result1 + 1)
}, 1000)
})
}
// 受け取った値を別のAPIにリクエストを投げて値を取得する処理
function request3(result2: number): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result2 + 2)
}, 1000)
})
}
request1()
.then((result1) => {
return request2(result1)
})
.then((result2) => {
return request3(result2)
})
.then((result3) => {
console.log(result3) //=> 4
})
スッキリ書けるようになりました!
非同期処理が成功したとき、resolveに成功結果を渡す
非同期処理が失敗したとき、rejectにエラーオブジェクトなどを渡す
function request3(result2: number): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result2 + 2)
}, 1000)
})
}
request1()
.then((result1) => {
return request2(result1)
})
.then((result2) => {
return request3(result2)
})
.then((result3) => {
console.log(result3) //=> 4
})
Promiseの簡単な考え方を説明しました。
詳細については以下のドキュメントが分かりやすくまとめていただいてますので併せてご確認ください。
次のセクションではPromiseを利用した非同期処理をより簡単に書ける構文としてasync /awaitを説明します
Chapter3: async/await is 何?
Promiseを利用した非同期処理をより簡単に書ける構文としてasync /awaitが存在します。
この構文を利用することで、非同期処理をより同期処理と同じような文脈で書くことができるようになります。
async関数
関数の前にasync
をつけることで関数の戻り値は Promise{xxx}
になります
async function getHello(): Promise<string> {
return "Hello world";
}
getHello().then((result) => {
console.log(result); // -> "Hello world"
});
Promiseをそのまま返すとどうなるの?
Promise{Promise{}}
みたいにならない?
安心してください! 二重に Promise
がラップされることはありません
async function getHello(): Promise<string> {
return new Promise((resolve) => {
resolve("Hello world");
});
}
getHello().then((result) => {
console.log(result); // -> "Hello world"
});
await関数
awaitはPromiseの値が解決されるまで実行を待機して、解決された値を返します。
Promiseの値を取得できるまで待機してくれます。
awaitの注意点としてawaitはasync関数の中でのみ使えます。
function sumNumber() {
return Promise.resolve(1)
}
async function main() {
console.log(1 + sumNumber()) // 演算子 '+' を型 '1' および 'Promise<number>' に適用することはできません。
console.log(1 + (await sumNumber())) // 2
}
main()
参考文献
最後に
読んでいただきありがとうございます。
今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。