##概要
下記のようなasync関数内で、配列にmap関数で処理を行います。
export const handler=async(event: APIGatewayEvent, context:Context, callback:Callback)=>{ const array_1 = ["aaa","bb","cccc"] array_1.map((str:any)=>{ await updateProduct(str) }) }
export const updateProduct =async(str)=>{ const shopify = new Shopify2({ (省略) }) await shopify.product.update(str) .then((res:any)=>{ res }) .catch((res:any)=>{ res }) }
上記のawait式が「非同期関数内で使用出来ます」とコンパイルエラーになってしまいます。
handler関数でasync宣言しているのでmap内まで非同期が有効であると思うのでこのようなエラーになってしまい、困っております。
怪しい点などアドバイス頂けないでしょうか。
宜しくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
問題点はシンプルで、async関数直下でしかawait構文は使えません。
入れ子OKにするとでかいasyncメイン関数内ならどこでも使い放題みたいになってコードが崩壊しますからね。
なのでArray.prototype.map(以下map)内でawaitを使いたければmapに引数もasync関数にしてやる必要があります。
しかしmapでasync関数を突っ込んでもPromiseの配列になるだけです。
なので単純にasync関数を突っ込むだけでは上手く行きません。
Promise.allを使いましょう。
私はTypeScriptに関してはあまり詳しくないのでJavaScriptで書きますが、
解説付きコードで表現するとこんな感じです。
js
1const handler = async (event, context, callback) => { 2 const array_1 = ["aaa","bb","cccc"] 3 4 // mapのコールバック関数もasync関数でくくってしまう。 5 const array_2 = array_1.map(async str => 6 await updateProduct(str) 7 ) 8 9 // Array.prototype.map内でasync関数作ると 10 // Promiseインスタンスの配列が払い出される 11 console.log(array_2) // [Promise, Promise, ...] 12 13 // もしupdateProductがPromiseを返すならこれでOK 14 // const array_2 = array_1.map(updateProduct) 15 16 // Promise.allにPromiseインスタンスの配列を突っ込めば 17 // 全ての実行を待って配列を取り出す一つのPromiseインスタンスになる 18 // そうすればawaitで待ちつつ配列形式で値を取り出せる 19 const array_result = await Promise.all(array_2) 20}
ですが、そもそもupdateProduct
という名称から目的を推測するに、
非同期処理を伴う戻り値Promiseインスタンスですよね?
もし違うならawaitで待ちたいとかお前は何を言っているんだ?みたいな状態になってしまいますね。
上記を踏まえてシンプルに書くと、
最終形はこんな感じになっていると考えられます。
js
1const handler = async (event, context, callback) => { 2 const array_1 = ["aaa","bb","cccc"] 3 const result = await Promise.all( 4 array_1.map(updateProduct) 5 ) 6}
ただ外部のAPIと通信するのでPromise.allでは並列で1度にAPIを叩いてしまう処理になってしまいlimitを超えてしまう為、使用を控えております。
mapというのは値を関数に従って変形させた結果を取り出すという目的に使われるものなので、
高度な非同期処理を内包したものを作るには不向きです。
なので、基本的にはfor文でお行儀よく1個ずつ処理を走らせる方向で考えるのが自然だと思います。
それでもリスト操作でこなすなら
100個のリクエストというボールが入ったプールがあったとして、
バケツで5個ずつ組み上げる作戦があります。
Lodashのchunk関数が丁度それに使えます。
実装をコードになおすと下記のような感じ。
js
1const range = (a, b) => Array(b - a + 1).fill(0).map((_, i) => i + a); 2console.log(range(1, 10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 3 4const chunk = (arr, size) => arr.reduce((result, it, i) => { 5 if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = []; 6 result[Math.floor(i / size)].push(it); 7 return result; 8}, []); 9console.log(JSON.stringify(chunk(range(1, 21), 5), null, 2));
json
1[ 2 [ 3 1, 4 2, 5 3, 6 4, 7 5 8 ], 9 [ 10 6, 11 7, 12 8, 13 9, 14 10 15 ], 16 [ 17 11, 18 12, 19 13, 20 14, 21 15 22 ], 23 [ 24 16, 25 17, 26 18, 27 19, 28 20 29 ], 30 [ 31 21 32 ] 33]
これを実際に作るならreduceを使いながらthenを次々に転がすような実装になります。
js
1const chunk = (arr, size) => arr.reduce((result, it, i) => { 2 if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = [] 3 result[Math.floor(i / size)].push(it) 4 return result 5}, []) 6 7const handler = async (event, context, callback) => { 8 const array_1 = ["aaa","bb","cccc"] 9 const limit = 5 10 await chunk(array_1, limit).reduce((promise, arr) => { 11 promise.then(() => Promise.all( 12 arr.map(updateProduct) 13 )) 14 return promise 15 }, Promise.resolve(1)) 16}
まぁ、chunkのアイデアまでは良いと思いますが、
reduceは明らかにES5まで退行しているので、
素直にfor...ofを使った方が100倍見栄えは良いでしょう。
js
1const chunk = (arr, size) => arr.reduce((result, it, i) => { 2 if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = [] 3 result[Math.floor(i / size)].push(it) 4 return result 5}, []) 6 7const handler = async (event, context, callback) => { 8 const array_1 = ["aaa","bb","cccc"] 9 const limit = 5 10 for (const arr of chunk(array_1, limit)) { 11 await Promise.all( 12 arr.map(updateProduct) 13 ) 14 } 15}
最速で行きたいなら
stateを使って状態管理した方が良いでしょうね。
状態管理に依存するので使うとするならforEach1個だけで頑張るのが無難ですが、
だったらやはりfor...ofでよくね?と感じます。
まぁ、Babelはasync関数とfor...ofの同時利用を解決出来るか怪しいので、
forEachで書けそうなら頑張っても良いと思いますが。
js
1const waitFor = (ms) => new Promise(resolve) => 2const waitUpdate = async (state, limit) => { 3 while (true) { 4 if (state.map(it => it.loading).length < limit) break 5 await waitFor(1000) // リミット件数実行中なら1秒程度待つ 6 } 7} 8 9const handler = async (event, context, callback) => { 10 const array_1 = ["aaa","bb","cccc"] 11 const limit = 5 12 const states = array_1.map(str => ({str, loading: false})) 13 for (const state of states) { 14 await waitUpdate(states, limit) 15 state.loading = true 16 updateProduct(state.str).then(() => state.loading = false) 17 } 18}
大体こんな感じがゴールになるでしょう。
投稿2020/03/02 09:21
編集2020/03/05 12:55総合スコア21203
0
handler関数でasync宣言しているのでmap内まで非同期が有効であると思うので
残念ながら、await
はasync
関数内に直接書くことしかできません。
「コールバックを要さないfor
系の構文で回しながらawait
を行う(1個ずつ実行される)」もしくは「全部をmap
してPromise
の配列に変換した上で、await Promise.all(配列)
とする(全部並列実行)」などの手段が必要となります。
投稿2020/03/02 09:06
総合スコア146018
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/03/05 06:03
2020/03/05 12:07