Featured image of post 【JavaScript 函式】Promise / Async / Await

【JavaScript 函式】Promise / Async / Await

【JavaScript 函式】Promise / Async / Await

Photo by Mohammad Rahmani on Unsplash

使用 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 处理结果

参考资料

Donate KJ 贊助作者喝咖啡

如果這篇文章對你有幫助的話,可以透過下面支付方式贊助作者喝咖啡,如果有什麼建議或想說的話可以贊助並留言給我
If this article has been helpful to you, you can support the author by treating them to a coffee through the payment options below. If you have any suggestions or comments, feel free to sponsor and leave a message for me!
方式 Method 贊助 Donate
PayPal https://paypal.me/kejyun
綠界 ECPay https://p.ecpay.com.tw/AC218F1
歐付寶 OPay https://payment.opay.tw/Broadcaster/Donate/BD2BD896029F2155041C8C8FAED3A6F8
All rights reserved,未經允許不得隨意轉載
Built with Hugo
主题 StackJimmy 设计