teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

後半部を追加

2020/03/05 12:55

投稿

miyabi-sun
miyabi-sun

スコア21473

answer CHANGED
@@ -46,4 +46,141 @@
46
46
  array_1.map(updateProduct)
47
47
  )
48
48
  }
49
- ```
49
+ ```
50
+
51
+ ---
52
+
53
+ > ただ外部のAPIと通信するのでPromise.allでは並列で1度にAPIを叩いてしまう処理になってしまいlimitを超えてしまう為、使用を控えております。
54
+
55
+ mapというのは値を関数に従って変形させた結果を取り出すという目的に使われるものなので、
56
+ 高度な非同期処理を内包したものを作るには不向きです。
57
+ なので、基本的にはfor文でお行儀よく1個ずつ処理を走らせる方向で考えるのが自然だと思います。
58
+
59
+ それでもリスト操作でこなすなら
60
+ 100個のリクエストというボールが入ったプールがあったとして、
61
+ バケツで5個ずつ組み上げる作戦があります。
62
+
63
+ [Lodashのchunk](https://lodash.com/docs/4.17.15#chunk)関数が丁度それに使えます。
64
+ 実装をコードになおすと下記のような感じ。
65
+
66
+ ```js
67
+ const range = (a, b) => Array(b - a + 1).fill(0).map((_, i) => i + a);
68
+ console.log(range(1, 10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
69
+
70
+ const chunk = (arr, size) => arr.reduce((result, it, i) => {
71
+ if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = [];
72
+ result[Math.floor(i / size)].push(it);
73
+ return result;
74
+ }, []);
75
+ console.log(JSON.stringify(chunk(range(1, 21), 5), null, 2));
76
+ ```
77
+
78
+ ```json
79
+ [
80
+ [
81
+ 1,
82
+ 2,
83
+ 3,
84
+ 4,
85
+ 5
86
+ ],
87
+ [
88
+ 6,
89
+ 7,
90
+ 8,
91
+ 9,
92
+ 10
93
+ ],
94
+ [
95
+ 11,
96
+ 12,
97
+ 13,
98
+ 14,
99
+ 15
100
+ ],
101
+ [
102
+ 16,
103
+ 17,
104
+ 18,
105
+ 19,
106
+ 20
107
+ ],
108
+ [
109
+ 21
110
+ ]
111
+ ]
112
+ ```
113
+
114
+ これを実際に作るなら[reduce](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)を使いながらthenを次々に転がすような実装になります。
115
+
116
+ ```js
117
+ const chunk = (arr, size) => arr.reduce((result, it, i) => {
118
+ if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = []
119
+ result[Math.floor(i / size)].push(it)
120
+ return result
121
+ }, [])
122
+
123
+ const handler = async (event, context, callback) => {
124
+ const array_1 = ["aaa","bb","cccc"]
125
+ const limit = 5
126
+ await chunk(array_1, limit).reduce((promise, arr) => {
127
+ promise.then(() => Promise.all(
128
+ arr.map(updateProduct)
129
+ ))
130
+ return promise
131
+ }, Promise.resolve(1))
132
+ }
133
+ ```
134
+
135
+ まぁ、chunkのアイデアまでは良いと思いますが、
136
+ reduceは明らかにES5まで退行しているので、
137
+ 素直に[for...of](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of)を使った方が100倍見栄えは良いでしょう。
138
+
139
+ ```js
140
+ const chunk = (arr, size) => arr.reduce((result, it, i) => {
141
+ if (!result[Math.floor(i / size)]) result[Math.floor(i / size)] = []
142
+ result[Math.floor(i / size)].push(it)
143
+ return result
144
+ }, [])
145
+
146
+ const handler = async (event, context, callback) => {
147
+ const array_1 = ["aaa","bb","cccc"]
148
+ const limit = 5
149
+ for (const arr of chunk(array_1, limit)) {
150
+ await Promise.all(
151
+ arr.map(updateProduct)
152
+ )
153
+ }
154
+ }
155
+ ```
156
+
157
+ 最速で行きたいなら
158
+ stateを使って状態管理した方が良いでしょうね。
159
+ 状態管理に依存するので使うとするならforEach1個だけで頑張るのが無難ですが、
160
+ だったらやはりfor...ofでよくね?と感じます。
161
+
162
+ まぁ、Babelはasync関数とfor...ofの同時利用を解決出来るか怪しいので、
163
+ forEachで書けそうなら頑張っても良いと思いますが。
164
+
165
+ ```js
166
+ const waitFor = (ms) => new Promise(resolve) =>
167
+ const waitUpdate = async (state, limit) => {
168
+ while (true) {
169
+ if (state.map(it => it.loading).length < limit) break
170
+ await waitFor(1000) // リミット件数実行中なら1秒程度待つ
171
+ }
172
+ }
173
+
174
+ const handler = async (event, context, callback) => {
175
+ const array_1 = ["aaa","bb","cccc"]
176
+ const limit = 5
177
+ const states = array_1.map(str => ({str, loading: false}))
178
+ for (const state of states) {
179
+ await waitUpdate(states, limit)
180
+ state.loading = true
181
+ updateProduct(state.str).then(() => state.loading = false)
182
+ }
183
+ }
184
+ ```
185
+
186
+ 大体こんな感じがゴールになるでしょう。

1

冒頭の解説を追加

2020/03/05 12:55

投稿

miyabi-sun
miyabi-sun

スコア21473

answer CHANGED
@@ -1,4 +1,12 @@
1
+ 問題点はシンプルで、async関数直下でしかawait構文は使えません。
2
+ 入れ子OKにするとでかいasyncメイン関数内ならどこでも使い放題みたいになってコードが崩壊しますからね。
3
+ なので[Array.prototype.map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map)(以下map)内でawaitを使いたければmapに引数もasync関数にしてやる必要があります。
4
+
5
+ しかしmapでasync関数を突っ込んでもPromiseの配列になるだけです。
6
+ なので単純にasync関数を突っ込むだけでは上手く行きません。
7
+ [Promise.all](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)を使いましょう。
8
+
1
- 私はTypeScriptに関してはあまり詳しくないのでJavaScriptで書きます
9
+ 私はTypeScriptに関してはあまり詳しくないのでJavaScriptで書きますが、
2
10
  解説付きコードで表現するとこんな感じです。
3
11
 
4
12
  ```js
@@ -14,7 +22,7 @@
14
22
  // Promiseインスタンスの配列が払い出される
15
23
  console.log(array_2) // [Promise, Promise, ...]
16
24
 
17
- // なのでupdateProductがPromiseを返すならこれでOK
25
+ // もしupdateProductがPromiseを返すならこれでOK
18
26
  // const array_2 = array_1.map(updateProduct)
19
27
 
20
28
  // Promise.allにPromiseインスタンスの配列を突っ込めば
@@ -22,8 +30,16 @@
22
30
  // そうすればawaitで待ちつつ配列形式で値を取り出せる
23
31
  const array_result = await Promise.all(array_2)
24
32
  }
33
+ ```
25
34
 
35
+ ですが、そもそも`updateProduct`という名称から目的を推測するに、
36
+ 非同期処理を伴う戻り値Promiseインスタンスですよね?
37
+ もし違うならawaitで待ちたいとかお前は何を言っているんだ?みたいな状態になってしまいますね。
38
+
26
- // 上記を踏まえてシンプルに書くとこんな感じ
39
+ 上記を踏まえてシンプルに書くと
40
+ 最終形はこんな感じになっていると考えられます。
41
+
42
+ ```js
27
43
  const handler = async (event, context, callback) => {
28
44
  const array_1 = ["aaa","bb","cccc"]
29
45
  const result = await Promise.all(