2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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) 関数の外の値が書き換わるまで待って続行

<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()) にするんだぞ!
めんどくさいぞ!!!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?