JavaScript Promise, await, Promise.all() 一部処理だけ待ちたい/同期させたい/他は即実行させたい場合


# Promise.all系の特徴
列挙されているtaskは必ず一気に実行されてしまう。
Aが成功したときだけBに行く、という処理はできない。
そうしたい場合の選択肢は4つ


1. そもそもAとBがヒトツナギなら、BをAの中に書けばいいやん
2. 全部一気に実行 → 各taskの中で、同期したいtaskの完了をpollingして待つ。
3. ひとつずつ実行する。Aが成功してたらBも実行、というように。
4. 同期したいのはawaitで(3)ぽく、非同期で一気に行きたいのは(2)風に


この記事はこの中の 2 と 3 を扱う。実際のコーディングでは 4 を採用したが、2+3で4ができる仕組み。


# (a) 関数の外の値が書き換わるまで待って続行
```html
<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になるまで) 待ったことがわかる

```ruby
apiMock is done. res=1000
updated=1000
```


# (b) Promise.all() の中で一部のpromiseだけ同期実行したい（待たせたい、遅らせたい）

apiMock()は処理が終わるまで時間がかかる。終わったら updated flagを書き換える。
apiWait()は即実行されるが、そのフラグが変わるまで待つ。

```html

<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() が待ったことがわかる。

```ruby
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にしないと同期しないのがめんどくさい。

```html
<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が出ない。

```ruby
apiMock done
apiNoWait done
apiWait done
```



# (d) promiseのひとつが fail したときの処理

この3連続awaitにおいて

```js
await apiMock();
await apiNoWait();
await apiWait();
```

例えば apiMock() がエラーで終わると、残りは実行されない

```js
Uncaught Error: apiMock is failed.
    at <anonymous>:13:13
// あとのawaitが走らない
```


## (e) エラーだと then が実行されない Promise.all

これをやると一気にスタートしてしまうので同期を含みたい場合は内部で待つ処理が必要。

ひとつでもrejectされると全体が thenが実行されない（ finished が出ることがない）

```html
<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が走る。

```ruby
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()

```html
<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されている。

```html
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が失敗しても実行される。

```html
<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が実行されていない。

```ruby
index.html:30 Error: apiMock is failed
index.html:16 apiNoWait done
```

# まとめ

awaitが動かなくなったら、目の前にあるすべての `()` を `async()` にしよう！！！！
目の前にあるすべての関数の前に `await` をつけよう！！！ `await setTimeout(async())` にするんだぞ！
めんどくさいぞ！！！！