ハイボール片手に。今日は Promise やるぞー。
## 13.2 Promises
- callback だとネストが深くなる→ Promise だとリニアに書ける
- callback だとエラーハンドリングが難しい→ Promise だとエラー処理の方法が標準化されてる
わかる。
Promiseの用語
- "fulfillled" - 最初のコールバックが呼び出されるとき
- "rejected" - 2番目のコールバックが呼び出されるとき
- "settled" - "fulfilled" か "rejected" になったとき
- "pending" - まだ "fulfilled" でも "rejected" でもないとき
- "resolved" - あとで説明する
"resolved" かー、なるほど?(わかってない
Promiseのチェーン
then()
は新たな Promise オブジェクトを返す(p1 と呼ぶことにする)。この Promise はthen()
に渡した処理が終わると fulfilled になる。
もう少し詳しく言うと(resolvedの話)
then()
に渡した処理が Promise 以外の値を返すと、then()
の返した Promise (p1) は fulfilled になるthen()
に渡した処理が Promise を返すと(p2)、then()
の返した Promise (p1) は resolved にはなるが fulfilled にはならない- p2 が fulfilled になると p2 と同じ値で p1 も fulfilled になる
- p2 が rejected になると p2 と同じ値で p1 も rejected になる
へー。 resolved と settled が違うんだねー。 Promise が Promise を処理するときに差が出てくる感じだな。
エラーハンドリング
.then()
の2番目の引数に関数を渡してあげると、処理中に例外が発生したときにその関数が呼び出される- けど、実際はそうすることは少ない。
.catch()
を使う。.catch(handleError)
は.then(null, handleError)
のショートハンド - 例外が発生したら、そこから
.catch()
があるところまでチェーンを降りていく .catch()
の後に処理が続く場合、.catch()
が普通に値を返すと、その次の処理にうつる
finally()
- ES2018
- コールバック関数は引数を受け取らない
.finally()
のコールバック関数が返す値は基本的には無視される.finally()
で返された Promise は、その前の Promise の結果の resolved または rejected の値を返す- ただし
.finally()
で例外が投げられた場合はその値で rejected になる
へー。1個前の値が渡されるのかー。
Promise.all()
- を使うと並列に実行することができる
- Promise の配列を引数として受け取る
- 戻り値の Promise は、入力値の Promise が1つでも rejected だったら rejected になる
- 最初に rejected になった Promise が出た時点で、まだ実行中の Promise があっても、すぐに rejected になる
const urls = [ /* URLs */ ]; let promises = urls.map(url => fetch(url).then(r => r.text())); Promise.all(promises) .then(bodies => { /* 処理 */ }) .catch(e => console.error(e));
Promise.all()
は実はもうちょっとフレキシブルで、入力値の配列の要素は Promise じゃなくても大丈夫。その場合は fulfilled された Promise のように扱われて、そのまま出力配列にコピーされる
あんまり使わなさそうかなぁとは思うけど、一応片隅に置いとくかな。
Promise.allSettled()
- ES2020
all()
のように Promise の配列を受け取る- rejected になることはない。全ての Promise の処理が終わるのを待って fulfilled になる
- 結果はオブジェクトの配列になっていて、各要素に
status
プロパティがあり "fulfilled" または "rejected" がセットされている - "fulfilled" の場合は
value
プロパティに値が、"rejected" の場合はreason
プロパティにエラーまたはリジェクトの値が入っている
> Promise.allSettled([Promise.resolve(1), Promise.reject(2), 3]).then(results => console.log(results)); Promise { <pending> } > [ { status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 2 }, { status: 'fulfilled', value: 3 } ]
こっちのが all()
より扱いやすいね。好きな感じ。
Promise.race()
- 同時に実行して最初に fulfilled または rejected になったものだけを返す。
Promise を書く
- 非同期に処理する必要がないものを Promise にして返したい場合は
Promise.resolve()
とPromise.reject()
を使えばいい - 新たに Promise を生成したい場合は Promise コンストラクターを使う
Promise p = new Promise((resolve, reject) => { });
- resolve 関数を使うと resolved または fulfilled になる
- reject 関数を使うと rejected になる
## 13.3 acync and await
- ES2017
- この2つの新しいキーワードのおかげで Promise がすごく簡単に使えるようになって、Promise ベースの非同期のコードが同期のコードみたいに書けるようになった
await
await
キーワードは Promise を受け取って、それを通常の値または例外に変換する。
let response = await fetch("/api/user/profile");
- 同期処理のように書けるけど、実際は非同期処理になっている。なので
await
を使う関数それ自体も非同期な関数じゃないといけない。
try catch 書けるの幸せ。
async
await
キーワードはasync
キーワードがついた関数の中でしか使えない、というルール。
async function getHighScore() { let response = await fetch("/api/user/profile"); let profile = await response.json(); return profile.highScore; }
async
な関数の戻り値は Promise になる。通常の値を返していたらそれが resolve された Promise を、例外を投げたらその例外で reject された Promise を返す。
お、あっさり async await が終わってしまった。シンプルでパワフルだなー。
## 13.4 Asynchronous Iteration
for/await
ループ
- ES2018
単に Promise の配列の場合、こんな風に書くことができるけど:
for(const promise of promises) { response = await promise; handle(response); }
for/await
ループを使うとシンプルにこう書ける:
for await (const response of promises) { handle(response); }
これは普通の Iterator を使った場合で、ちょっとシンプルになるくらいだけど、Asynchronous Iterator の場合はもっと面白い。
Asynchronous Iterator
Symbol.iterator
じゃなくてSymbol.asyncIterator
でメソッドを定義する。next()
は Promise を返す。
ちなみに for/await
は Symbol.iterator
に対しても動くけど、まず先に Symbol.asyncIterator
が定義されていないかを探す。
Asynchronous Generators
async function *
で Asynchronous Generator を定義できる- 内部では
await
を利用できる yield
で返される値は自動的に Promise でラッピングされる
async function* clock() { for(let count = 1; count <= 10; count++) { await new Promise(resolove => setTimeout(resolove, 100)); yield count; } } async function test() { for await (let tick of clock()) { console.log(tick); } } test();
今まで勉強してきたのの組み合わせって感じね。
Asynchronous Iterators
Symbol.asyncIterator
でメソッドを定義するだけだな。
class Clock { async *[Symbol.asyncIterator]() { for(let count = 1; count <= 10; count++) { await new Promise(resolove => setTimeout(resolove, 100)); yield count; } } } async function test() { for await (let tick of new Clock()) { console.log(tick); } }
また別の例で、AsyncQueue を実装して、enqueue より先に dequeue を呼び出すことができるのとか面白かった。dequeue が unresolved な Promise を返すから。
Async なイテレーションは、実際にそういうのを使いたい場面に出くわしたら、もっとしっくりくるんだろうな。
はい、今日も面白かったー。