Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Promise.all()内で軽率にawaitしてはいけない

$
0
0

事前知識

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プロパティへアクセスした結果、responsePromiseundefinedとなり、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していると非合理的なコードを無意識に書く羽目になるので気をつけましょう!!!


Viewing all articles
Browse latest Browse all 8691

Trending Articles