事前知識
Promise.all()
Promise.all(iterable) メソッドは単一の Promise を返し、これは引数 iterable の中のすべての Promise が解決されるか、引数 iterable の中に Promise がない場合に解決されます。最初に拒否された Promise の拒否理由をもって拒否されます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
JSONPlaceholder
例に用いるAPI
JSONを返してくれる
https://jsonplaceholder.typicode.com/
Promiseを返すことを意識する
Promiseを返さないとPromise.all()を使う意味が無い。。。
見かけ上並列で非同期処理を実行したい
下記のように、urlの配列があったとする
consturls=['https://jsonplaceholder.typicode.com/todos/1','https://jsonplaceholder.typicode.com/todos/2','https://jsonplaceholder.typicode.com/todos/3']
それぞれのURLに対しGETリクエストを投げ、データを得たい。
その場合、urlsをマップしてPrimse.all()の引数に渡せばいい
constresponses=awaitPromise.all(urls.map(url=>{constresponsePromise=axios.get(url)returnresponsePromise}))
ちなみに上記は丁寧な書き方で、下記のようにスッキリさせることができる
constresponses=awaitPromise.all(urls.map(axios.get))
そうすると、responses
には下記が得られる。
[{status:200,statusText:'OK',headers:{...},config:{...},request:ClientRequest{...},data:{userId:1,id:1,title:'delectus aut autem',completed:false}},{status:200,statusText:'OK',headers:{...},config:{...},request:ClientRequest{...},data:{userId:1,id:2,title:'quis ut nam facilis et officia qui',completed:false}},{status:200,statusText:'OK',headers:{...},config:{...},request:ClientRequest{...},data:{userId:1,id:3,title:'fugiat veniam minus',completed:false}}]
確かにレスポンスが得られていることがわかる。
ここで、レスポンスのdata
プロパティにアクセスしたい場合にどうするか。
方法は2つ思い浮かぶと思う。
1️⃣. Promise.all()内のmapのreturnのタイミングでdata
プロパティにアクセスする
2️⃣. Promise.all()の外でアクセスする
私はさっきのコード中で、何も考えずに
constresponses=awaitPromise.all(urls.map(url=>{constresponsePromise=axios.get(url)returnresponsePromise.data// <- change point}))
としてみた。選択肢の1️⃣の方法である。
するとresponses
の中身は
[undefined,undefined,undefined]
である。
どういうことか
結論、urls.map()
に渡される無名関数が同期処理だからである。const responsePromise = axios.get(url)
で得られるresponsePromise
の中身は、Promise { <pending> }
である為、当然data
プロパティもまだ存在していない。
Promise { <pending> }
の存在しないdata
プロパティへアクセスした結果、responsePromise
はundefined
となり、undefined
がreturnされる。
なので、愚かな私はaxios.get(url)
をawait
すればよいと考える。
constresponses=awaitPromise.all(urls.map(asyncurl=>{// <- change point 1constresponse=awaitaxios.get(url)// <- change point 2returnresponse.data// <- change point 3}))
こうすると、
[{userId:1,id:1,title:'delectus aut autem',completed:false},{userId:1,id:2,title:'quis ut nam facilis et officia qui',completed:false},{userId:1,id:3,title:'fugiat veniam minus',completed:false}]
responses
には上記が得られる。
しかし、少し考えて違和感に気づく
Promise返してなくね?
Promiseを返さないとPromise.all()を使う意味が無い。。。
冒頭で言いました
下記は先程のコードですが、❗️
で強調した行において既にPromiseがresolveしているのですね。(だからdataプロパティにアクセスできる)
constresponses=awaitPromise.all(urls.map(asyncurl=>{constresponse=awaitaxios.get(url)// <- ❗️returnresponse.data}))
なので。単にdata
プロパティが欲しいだけであればこの方法は適していない
最適解
constresponses=awaitPromise.all(urls.map(axios.get))constdatas=responses.map(response=>response.data)
2行で終わりです。綺麗
私のようにPromise
の理解を疎かにしていて、軽率にawait
していると非合理的なコードを無意識に書く羽目になるので気をつけましょう!!!