Promise / Async / Await

Node.js Promise / Async / Await 同步處理

使用 async / await 可以避免 callback hell,程式可讀性也比 Promise 更好,也可以達到同步處理的效果

函式說明

函式變數 說明
Promise.resolve() 回傳資料給 then() 通知已執行完成
Promise.reject() 回傳資料給 catch() 通知執行失敗
Promise.then() 取得 resolve() 回傳的資料,執行成功
Promise.catch() 取得 reject() 回傳的資料,執行失敗
Promise.finally() 整個 Promise 執行結束,不管成功失敗,都會呼叫的函式
Promise.all() 確認所有陣列中的 Promise 都有執行完成
Promise.pending 等待
Promise.fulfilled 成功
Promise.rejected 失敗
async 告訴這個函式是 Promise 函式,可以使用 then()catch() 去接收處理後的資料
await 只能出現在 async 函式中,會等待指定的 Promise 函式執行完再執行

使用 Promise

建立 Promise 物件

const logAsync = (message, time) => {
    // 建立 Promise 物件,處理異步資訊
    return new Promise((resolve, reject) => {
        if (message && time) {
            // 設定指定時間顯示訊息
            setTimeout(() => {
                console.log(message);
                // 執行正常,呼叫 resolve 通知已處理完成
                resolve(`從 resolve 取得:${message}`)
            }, time);
        } else {
            // 處理異常,呼叫 reject 通知處理錯誤
            reject(`無法處理 reject: ${message} & ${time}`);
        }
    });
};

使用 then() 取得 Promise 呼叫的 resolve

logAsync('這個訊息過 1 秒才會出現', 1000)
    .then((log1_resolve_message) => {
        console.log(log1_resolve_message);
        return logAsync('這個訊息再過 1.5 秒才會出現', 1500);
    })
    .then((log2_resolve_message) => {
        console.log(log2_resolve_message);
        return logAsync('這個訊息再過 2 秒才會出現', 2000);
    }).then((log3_resolve_message) => {
        console.log(log3_resolve_message);
    });

// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 這個訊息再過 1.5 秒才會出現
// 從 resolve 取得:這個訊息再過 1.5 秒才會出現
// 這個訊息再過 2 秒才會出現
// 從 resolve 取得:這個訊息再過 2 秒才會出現

使用 async / await

  • awaitasync function 裡面才可以使用
const logMessage = async () => {
    let log1_resolve_message = await logAsync('1 秒後會出現這句', 1000);
    console.log(log1_resolve_message);
    let log2_resolve_message = await logAsync('再 1.5 秒後會出現這句', 1500);
    console.log(log2_resolve_message);
    let log3_resolve_message = await logAsync('再 2 秒後會出現這句', 2000);
    console.log(log3_resolve_message);
};

logMessage();

// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 這個訊息再過 1.5 秒才會出現
// 從 resolve 取得:這個訊息再過 1.5 秒才會出現
// 這個訊息再過 2 秒才會出現
// 從 resolve 取得:這個訊息再過 2 秒才會出現

使用 await Promise.all() 執行

  • awaitasync function 裡面才可以使用
(async () => {
    let allLogMessage = await Promise.all([
        logAsync('1 秒後會出現這句', 1000),
        logAsync('再 1.5 秒後會出現這句', 1500),
        logAsync('再 2 秒後會出現這句', 2000)
    ]);
    console.log(allLogMessage);
})();

// 1 秒後會出現這句
// 再 1.5 秒後會出現這句
// 再 2 秒後會出現這句
// [
//   '從 resolve 取得:1 秒後會出現這句',
//   '從 resolve 取得:再 1.5 秒後會出現這句',
//   '從 resolve 取得:再 2 秒後會出現這句'
// ]

全部在 Promise 中 resolve 的訊息,會用陣列的方式傳給 await 前面的變數

使用 catch 抓取 reject() 傳的錯誤訊息

在第 2 次呼叫 1.5 秒時,故意沒有傳秒數,導致程式發生錯誤呼叫 reject

logAsync('這個訊息過 1 秒才會出現', 1000)
    .then((log1_resolve_message) => {
        console.log(log1_resolve_message);
        // 在這裡故意沒有傳秒數,導致程式發生錯誤呼叫 reject
        return logAsync('這個訊息再過 1.5 秒才會出現');
    })
    .then((log2_resolve_message) => {
        console.log(log2_resolve_message);
        return logAsync('這個訊息再過 2 秒才會出現', 2000);
    })
    .then((log3_resolve_message) => {
        console.log(log3_resolve_message);
    })
    .catch((error) => {
        console.log(error);
    });

// 這個訊息過 1 秒才會出現
// 從 resolve 取得:這個訊息過 1 秒才會出現
// 無法處理 reject: 這個訊息再過 1.5 秒才會出現 & undefined

這裡會發現 第 1 秒 有處理,但 1.5 秒 時就發生錯誤,後續的 2 秒 就沒執行了

使用 Promise.all() catch 抓取 reject() 傳的錯誤訊息

(async () => {
    let allLogMessage = await Promise.all([
        logAsync('1 秒後會出現這句', 1000),
        logAsync('再 1.5 秒後會出現這句'),
        logAsync('再 2 秒後會出現這句', 2000)
    ]);

    console.log('----');
    console.log(allLogMessage);
    console.log('----');
})().catch((error) => {
    console.log(error);
});

// 無法處理 reject: 再 1.5 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句

在這裡發現,再 1.5 秒的時候直接 catch 到錯誤,直接顯示錯誤訊息

1 秒2 秒 的部分皆有執行,所以表示所有的工作都是異步執行的

只是因為有任一個發生錯誤,所以 allLogMessage 就沒有取得訊息,直接沒有執行

將 Async 包成一個變數

const logMesssageAsync = async () => {
    let allLogMessage = await Promise.all([
        logAsync('1 秒後會出現這句', 1000),
        logAsync('再 2 秒後會出現這句'),
        logAsync('再 2 秒後會出現這句', 2000)
    ]);

    console.log('----');
    console.log(allLogMessage);
    console.log('----');
};

let getLogMessageAsync = logMesssageAsync()
    .then((success_message) => {
        console.log(success_message)
    }).catch((error) => {
        console.log(error);
    });

console.log(getLogMessageAsync);

// Promise { <pending> }
// 無法處理 reject: 再 2 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句

因為 logMesssageAsync 被告之為 async 函式,所以在印出執行後的結果會拿到一個 Promise 物件 Promise { <pending> },就可以把這個變數當作 Promise 方式去操作

所以也可以寫成這樣,會得到一樣的訊息

let getLogMessageAsync = logMesssageAsync();

console.log(getLogMessageAsync);

getLogMessageAsync
    .then((success_message) => {
        console.log(success_message)
    }).catch((error) => {
        console.log(error);
    });
// Promise { <pending> }
// 無法處理 reject: 再 2 秒後會出現這句 & undefined
// 1 秒後會出現這句
// 再 2 秒後會出現這句

Promise 鏈

一直不斷使用 then() 可以不斷處理上一個 then() 處理的資料

new Promise(function(resolve, reject) {

    setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

    console.log(result); // 1
    return result * 2;

}).then(function(result) { // (***)

    console.log(result); // 2
    return result * 2;

}).then(function(result) {

    console.log(result); // 4
    return result * 2;

});

Promise / Async / Await

在迴圈使用 async / await

1. 使用 for await…of,每個 Promise 結束後可以馬上取得結果


const sleep = (miniSecond) => {
    return new Promise((resolve, reject) => {
        console.log(`[sleep 秒數 - ${miniSecond}] enter`);
        setTimeout(() => {
            console.log(`[sleep 秒數 - ${miniSecond}] resolve`);
            resolve(`秒數 ${miniSecond} 處理結果`);
        }, miniSecond);
    });
};

// 所有的 Promise 會在此時就發動
const SleepPromises = [sleep(1000), sleep(5000), sleep(2000)];

// 方法一:使用 for await,每個 Promise 結束後可以馬上取得結果
(async function () {
    for await (const item of SleepPromises) {
        console.log('[for await 結果] ', item);
    }
})();

// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for await 結果]  秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for await 結果]  秒數 5000 處理結果
// [for await 結果]  秒數 2000 處理結果

依序執行的順序是 100050002000 毫秒

上面可以看到,秒數 2000 的已經優先 resolve 了,但是 for await 在等第 2 個 5000 的結果處理完,才去處理 2000 的結果

2. 使用 for…of,每個 Promise 結束後可以馬上取得結果

// 使用 for...of,每個 Promise 結束後可以馬上取得結果
(async function () {
    for (const item of SleepPromises) {
        // 等待處理結果
        const result = await item;
        console.log('[for of 結果] ', result);
    }
})();

// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for of 結果]  秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for of 結果]  秒數 5000 處理結果
// [for of 結果]  秒數 2000 處理結果

await 寫在 for 迴圈中,一樣會依序等待處理結果

3. 使用 for 迴圈,每個 Promise 結束後可以馬上取得結果

// 使用 for 迴圈,每個 Promise 結束後可以馬上取得結果
(async function () {
    for (let i = 0; i < SleepPromises.length; i++) {
        // 等待處理結果
        const result = await SleepPromises[i];
        console.log('[for] 結果', result);
    }
})();

// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [for] 結果 秒數 1000 處理結果
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for] 結果 秒數 5000 處理結果
// [for] 結果 秒數 2000 處理結果

4. 使用 Promise.all,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止

// 使用 Promise.all,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止
(async function () {
    const results = await Promise.all(SleepPromises);
    console.log('[Promise.all] 結果', results);
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [Promise.all] 結果 [ '秒數 1000 處理結果', '秒數 5000 處理結果', '秒數 2000 處理結果' ]

5. 使用 Promise.allSettled,一次取得所有結果,需等待所有 Promise 都 resolve(fulfilled) / reject(rejected) 後終止

// 使用 Promise.allSettled,一次取得所有結果,需等待所有 Promise 都 resolve(fulfilled) / reject(rejected) 後終止
(async function () {
    const results = await Promise.allSettled(SleepPromises);
    console.log('[Promise.allSettled] 結果', results);
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [Promise.allSettled] 結果 [
//   { status: 'fulfilled', value: '秒數 1000 處理結果' },
//   { status: 'fulfilled', value: '秒數 5000 處理結果' },
//   { status: 'fulfilled', value: '秒數 2000 處理結果' }
// ]

6. 使用 for…of await,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止

不如就用 Promise.all 吧

// 使用 for...of await,一次取得所有結果,需等待所有 Promise resolve 或其中一個被 reject 時終止 吧)
(async function () {
    for (const result of await Promise.all(SleepPromises)) {
        console.log('[for...of await] 結果', result);
    }
})();
// [sleep 秒數 - 1000] enter
// [sleep 秒數 - 5000] enter
// [sleep 秒數 - 2000] enter
// [sleep 秒數 - 1000] resolve
// [sleep 秒數 - 2000] resolve
// [sleep 秒數 - 5000] resolve
// [for...of await] 結果 秒數 1000 處理結果
// [for...of await] 結果 秒數 5000 處理結果
// [for...of await] 結果 秒數 2000 處理結果

參考資料