Promise.all系の特徴
列挙されているtaskは必ず一気に実行されてしまう。
Aが成功したときだけBに行く、という処理はできない。
そうしたい場合の選択肢は4つ
- そもそもAとBがヒトツナギなら、BをAの中に書けばいいやん
- 全部一気に実行 → 各taskの中で、同期したいtaskの完了をpollingして待つ。
- ひとつずつ実行する。Aが成功してたらBも実行、というように。
- 同期したいのはawaitで(3)ぽく、非同期で一気に行きたいのは(2)風に
この記事はこの中の 2 と 3 を扱う。実際のコーディングでは 4 を採用したが、2+3で4ができる仕組み。
(a) 関数の外の値が書き換わるまで待って続行
<script>
let updated = 0;
apiMock = (i) => {
return new Promise((resolve) => {
setTimeout(function(){
updated = i;
resolve(i);
}, i);
});
}
async function start() {
res = await apiMock(1000);
console.log(`apiMock is done. res=${res}`);
console.log(`updated=${updated}`);
}
start();
</script>
result
console.logがapiMock()が終わるまで(updatedが1000になるまで) 待ったことがわかる
apiMock is done. res=1000
updated=1000
(b) Promise.all() の中で一部のpromiseだけ同期実行したい(待たせたい、遅らせたい)
apiMock()は処理が終わるまで時間がかかる。終わったら updated flagを書き換える。
apiWait()は即実行されるが、そのフラグが変わるまで待つ。
<script>
let updated = 0;
// Task1: Wait 1 sec
apiMock = new Promise((resolve) => {
setTimeout(function(){
updated = 1500;
resolve(1500);
console.log("apiMock done");
}, 1500);
});
// Task2: Wait 0 sec
apiNoWait = new Promise((resolve) => {
resolve(1000);
console.log("apiNoWait done");
});
// Task3: Wait until task1 is done
apiWait = new Promise((resolve, reject) => {
let maxTry = 0;
const interval = setInterval(async () => {
if (maxTry++ > 100) { // give up after x tries
reject(`api does not respond`);
clearInterval(interval);
} else if (updated > 0) {
resolve(updated);
clearInterval(interval);
resolve(100);
console.log("apiWait done");
} else {
console.log(`apiWait had waited ${maxTry} times ...`);
}
}, 300);
});
// 不要。new Promiseだけで走り出す。
// () => Promise.all([apiMock]);
/* もし すべて終わった後に処理をしたければ then する
Promise.all([apiMock, apiNoWait, apiWait]).then((p) => {
console.log("finished");
console.log(p); // 全taskの戻り値(resolveの引数)が帰る → [1500, 1000, 1500]
});
*/
</script>
result
had waited n times... で apiWait() が待ったことがわかる。
VM129:21 apiNoWait done
Promise {<pending>}
VM129:38 apiWait had waited 1 times ...
VM129:38 apiWait had waited 2 times ...
VM129:38 apiWait had waited 3 times ...
VM129:38 apiWait had waited 4 times ...
VM129:14 apiMock done
VM129:36 apiWait done
(c) 開始タイミングを完璧に同期的にしたい
apiMock()の中の new Promise も await + asyncにしないと同期しないのがめんどくさい。
<script>
let updated = 0;
// Task1: Wait 1 sec
apiMock = async () => {
await new Promise(async (resolve) => {
setTimeout(function(){
updated = 1500;
resolve(1500);
console.log("apiMock done");
}, 1500);
});
}
// Task2: Wait 0 sec
apiNoWait = async () => {
await new Promise(async (resolve) => {
resolve(1000);
console.log("apiNoWait done");
});
}
// Task3: Wait until task1 is done
apiWait = async () => {
await new Promise(async (resolve, reject) => {
let maxTry = 0;
const interval = setInterval(async () => {
if (maxTry++ > 100) { // give up after x tries
reject(`api does not respond`);
clearInterval(interval);
} else if (updated > 0) {
resolve(updated);
clearInterval(interval);
resolve(100);
console.log("apiWait done");
} else {
console.log(`apiWait had waited ${maxTry} times ...`);
}
}, 300);
});
}
const start = async () => {
await apiMock();
await apiNoWait();
await apiWait();
}
start();
</script>
result
apiMockが終わった後にapiWait()が走るので、 待ってます... messageが出ない。
apiMock done
apiNoWait done
apiWait done
(d) promiseのひとつが fail したときの処理
この3連続awaitにおいて
await apiMock();
await apiNoWait();
await apiWait();
例えば apiMock() がエラーで終わると、残りは実行されない
Uncaught Error: apiMock is failed.
at <anonymous>:13:13
// あとのawaitが走らない
(e) エラーだと then が実行されない Promise.all
これをやると一気にスタートしてしまうので同期を含みたい場合は内部で待つ処理が必要。
ひとつでもrejectされると全体が thenが実行されない( finished が出ることがない)
<script>
let updated = 0;
// Task1: Wait 1 sec
apiMock = new Promise((resolve) => {
throw new Error("api failed"); // <--------- エラー
);
// Task2: Wait 0 sec
apiNoWait = new Promise((resolve) => {
resolve(1000);
console.log("apiNoWait done");
});
// Task3: Wait until task1 is done
apiWait = new Promise((resolve, reject) => {
let maxTry = 0;
const interval = setInterval(() => {
if (maxTry++ > 50) { // give up after x tries
reject(`api does not respond`);
clearInterval(interval);
} else if (updated > 0) {
resolve(updated);
clearInterval(interval);
resolve(100);
console.log("apiWait done");
} else {
console.log(`apiWait had waited ${maxTry} times ...`);
}
}, 300);
});
Promise.all([apiMock, apiNoWait, apiWait]).then((p) => {
console.log("finished");
console.log(p);
}).catch((p) => {
console.log("Found an error:");
console.log(p);
});
</script>
result
thenが走らないのでfinishedが出ません。でも、 全taskがkickされてます。
最後にcatchがまた走るわけでもない。rejectされた瞬間にcatchが走る。
apiNoWait done
index.html:48 Found an error:
index.html:49 Error: api failed
at index.html:11
at new Promise (<anonymous>)
at index.html:10
index.html:39 apiWait had waited 1 times ...
index.html:39 apiWait had waited 2 times ...
index.html:39 apiWait had waited 3 times ...
...
index.html:39 apiWait had waited 50 times ...
index.html:39 apiWait had waited 51 times ...
(f) エラーでもthenを実行させる Promise.allSettled().then()
<script>
let updated = 0;
// Task1: Wait 1 sec
apiMock = new Promise((resolve) => {
throw new Error("api failed"); // <--------- エラー
);
// Task2: Wait 0 sec
apiNoWait = new Promise((resolve) => {
resolve(1000);
console.log("apiNoWait done");
});
// Task3: Wait until task1 is done
apiWait = new Promise((resolve, reject) => {
let maxTry = 0;
const interval = setInterval(() => {
if (maxTry++ > 50) { // give up after x tries
reject(`api does not respond`);
clearInterval(interval);
} else if (updated > 0) {
resolve(updated);
clearInterval(interval);
resolve(100);
console.log("apiWait done");
} else {
console.log(`apiWait had waited ${maxTry} times ...`);
}
}, 300);
});
Promise.allSettled([apiMock, apiNoWait, apiWait]).then((p) => {
console.log("finished");
console.log(p);
}).catch((p) => {
console.log("Found an error:");
console.log(p);
});
</script>
awaitと同じ。一気にスタートしてしまうのは Promise.allSetteled と同じ。
result
最後の戻り値の中で、エラー(reject)が有ったかどうかを確認できる。 p.reasonがあるとrejectされている。
apiNoWait done
index.html:39 apiWait had waited 1 times ...
index.html:39 apiWait had waited 2 times ...
...
index.html:39 apiWait had waited 51 times ...
index.html:45 finished
index.html:46 (3) [{…}, {…}, {…}]
0: {status: "rejected", reason: Error: api failed at file:///Desktop/index.html:11:8 at new Promise (<anonymo…}
1: {status: "fulfilled", value: 1000}
2: {status: "rejected", reason: "api does not respond"}
(g) awaitで同期しながら、エラー影響があるものだけ実行しない
apiMockが成功しない限りapiWaitは実行したくないので、セットでtryさせる。
apiNoWaitは関係ないので、apiMockが失敗しても実行される。
<script>
// Task1: Wait 1 sec
apiMock = async () => {
throw new Error("apiMock is failed"); // <-------- エラー
}
// Task2: Wait 0 sec
apiNoWait = async () => {
await new Promise(async (resolve) => {
resolve(1000);
console.log("apiNoWait done");
});
}
// Task3: Wait until task1 is done
apiWait = async () => {
console.log("apiWait done"); // <----- apiMockを待つ処理が不要になる
}
const start = async () => {
try {
await apiMock();
await apiWait();
} catch(e) {
console.error(e);
}
await apiNoWait();
}
start();
</script>
result
apiWaitが実行されていない。
index.html:30 Error: apiMock is failed
index.html:16 apiNoWait done
まとめ
awaitが動かなくなったら、目の前にあるすべての ()
を async()
にしよう!!!!
目の前にあるすべての関数の前に await
をつけよう!!! await setTimeout(async())
にするんだぞ!
めんどくさいぞ!!!!