JavaScript之Promise

98 阅读6分钟

JavaScript

Promise的基本概念

Promise 是一种代表异步操作最终完成(或失败)及其结果值的机制。它可以用来替代传统的回调函数,提供更加直观和易于管理的异步代码结构。

Promise 有三种状态:

  • Pending(待定):表示异步操作还未完成,结果尚未可得。
  • Fulfilled(已兑现):表示异步操作已成功完成,且返回结果。
  • Rejected(已拒绝):表示异步操作失败,且返回错误信息。

Promise的核心方法

  1. then():用于处理 Promise 成功(fulfilled)后的回调。当 Promise 被解决(resolved)时,它会执行传入的回调函数,并返回一个新的 Promise。

    promise.then(result => {
        console.log('成功:', result);
    }).catch(error => {
        console.log('失败:', error);
    });
    
  2. catch():用于处理 Promise 失败(rejected)后的回调,返回一个新的 Promise。

  3. finally():不论 Promise 最终是 resolved 还是 rejected,finally() 总会执行,通常用来执行清理操作。

Promise 链式调用

Promise 提供了链式调用的能力。因为每个 then()catch() 都返回一个新的 Promise,所以你可以把多个操作串联在一起。每次 then() 的返回值都会作为下一个 then() 的输入。

fetchData()
    .then(data => {
        console.log('数据获取成功', data);
        return processData(data); // 返回一个新的 Promise
    })
    .then(processedData => {
        console.log('数据处理成功', processedData);
    })
    .catch(error => {
        console.error('操作失败', error);
    })
    .finally(() => {
        console.log('操作结束');
    });

Promise.all 和 Promise.race

  • Promise.all():接受一个包含多个 Promise 的数组,只有当所有 Promise 都成功时,Promise.all() 才会返回成功。如果其中有一个失败,整个 Promise.all() 会失败。

    Promise.all([promise1, promise2, promise3])
        .then(results => {
            console.log('所有操作都成功:', results);
        })
        .catch(error => {
            console.error('其中一个操作失败:', error);
        });
    
  • Promise.race():返回第一个完成(不管是 resolved 还是 rejected)的 Promise 的结果。

    Promise.race([promise1, promise2, promise3])
        .then(result => {
            console.log('第一个完成:', result);
        })
        .catch(error => {
            console.error('第一个失败:', error);
        });
    

异常处理与链式错误捕获

`

Promise 提供了强大的错误处理能力。当链式调用中某个 Promise 被拒绝,catch() 会捕获到错误,并可以继续传递或处理后续的错误。这避免了传统回调方式中的错误被吞掉的情况。

doSomething()
    .then(result => {
        return doAnotherThing(result);
    })
    .catch(error => {
        console.error('出错了:', error);
    });

Promise的优化与进阶

  1. 异步操作的串行和并行处理

    • 串行处理:多个异步操作按顺序执行,后一个操作依赖于前一个操作的结果。
    • 并行处理:多个异步操作同时执行,只有当所有操作都完成时才继续。

    使用 Promise.all()Promise.allSettled() 进行并行操作。

  2. async/await 与 Promise 的关系asyncawait 是基于 Promise 实现的语法糖,它使得异步代码更加简洁和同步化。通过 await 可以暂停函数的执行直到 Promise 完成,从而避免了大量的回调函数或链式 then()

    async function fetchData() {
        try {
            let data = await fetch('https://api.example.com/data');
            let result = await data.json();
            console.log(result);
        } catch (error) {
            console.error(error);
        }
    }
    

Promise 内部实现原理

Promise 本质上是一个状态机,在处理异步操作时,会将状态(pending、fulfilled、rejected)存储在内部。每次调用 then()catch() 时,都会返回一个新的 Promise,且通过 .resolve().reject() 来改变状态。通过微任务队列来实现异步执行,以确保 then()catch() 的回调总是在当前执行栈清空后执行。

一些常见的 Promise 面试题及其答案如下:

1. Promise 与回调函数的区别是什么?

  • 回调函数:通过将函数作为参数传递给另一个函数来处理异步操作的结果。它可能导致“回调地狱”(callback hell),使得代码难以维护和理解。

  • Promise:是一个代表异步操作最终完成或失败的值。它允许通过 .then().catch().finally() 链式调用,从而避免了嵌套回调,并使代码更加简洁和可读。

2. Promise 的三种状态是什么?

Promise 具有三种状态:

  • Pending(待定):表示异步操作还未完成,结果尚不可得。

  • Fulfilled(已兑现):表示异步操作完成并成功返回结果。

  • Rejected(已拒绝):表示异步操作失败,返回错误信息。

3. 如何在 Promise 中处理错误?

可以通过 .catch().then() 的第二个回调函数来处理错误:

promise.then(result => {
    console.log(result);
}).catch(error => {
    console.error(error);  // 处理错误
});

或者:

promise.then(result => {
    console.log(result);
}, error => {
    console.error(error);  // 处理错误
});

4. 什么是 Promise.all()Promise.race()?它们有什么区别?

  • Promise.all():接受一个包含多个 Promise 的数组,只有当所有 Promise 都成功时,Promise.all() 才会返回成功。如果其中任何一个 Promise 被拒绝,整个 Promise.all() 会失败。

    Promise.all([promise1, promise2, promise3])
        .then(results => {
            console.log('所有 Promise 都成功:', results);
        })
        .catch(error => {
            console.error('其中一个失败:', error);
        });
    
  • Promise.race():返回第一个完成(无论是 fulfilled 还是 rejected)的 Promise 的结果,不管其他 Promise 是否已完成。

    Promise.race([promise1, promise2, promise3])
        .then(result => {
            console.log('第一个完成的 Promise:', result);
        })
        .catch(error => {
            console.error('第一个失败的 Promise:', error);
        });
    

5. 如何用 Promise 实现串行和并行执行异步任务?

  • 串行执行:可以通过在 .then() 中返回新的 Promise 来实现串行执行,后续的异步操作依赖于前一个操作的结果。

    promise1()
        .then(result => {
            return promise2(result); // 等待 promise1 执行完毕后再执行 promise2
        })
        .then(result2 => {
            console.log(result2);
        });
    
  • 并行执行:可以通过 Promise.all() 来实现并行执行多个异步任务,所有任务同时开始,只有当所有任务都完成时,才会继续执行后续操作。

    Promise.all([promise1(), promise2()])
        .then(results => {
            console.log('所有任务都完成:', results);
        });
    

6. 解释 async/awaitPromise 的关系。

async/await 是基于 Promise 的语法糖。它使得异步代码看起来像是同步代码,从而提高可读性。

  • async 标记函数为异步,返回一个 Promise
  • await 用于等待一个 Promise,它会暂停函数的执行,直到 Promise 完成。

示例:

async function fetchData() {
    try {
        let result = await fetch('https://api.example.com');
        let data = await result.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

7. 解释 Promise 的内部实现原理。

Promise 内部使用了一个状态机,管理 pendingfulfilledrejected 三种状态。每次调用 .then().catch() 时,都会返回一个新的 Promise,并且根据原 Promise 的状态来决定如何处理结果:

  • 如果 Promise 已经解决,then()catch() 会立即执行回调。
  • 如果 Promise 还在 pending 状态,回调函数会被添加到微任务队列(microtask queue)中,并在当前任务执行完之后执行。

8. 如何处理 Promise 的并发限制问题?

当你需要限制并发的 Promise 数量时,可以使用 async/awaitPromise.all(),或者使用第三方库如 p-limit 或自己实现一个并发限制器。例如,控制并发数量:

async function runTasksWithLimit(tasks, limit) {
    const result = [];
    const executing = [];

    for (const task of tasks) {
        const p = task().then(res => result.push(res));
        executing.push(p);

        if (executing.length >= limit) {
            await Promise.race(executing); // 等待最快的任务完成
            executing.splice(executing.findIndex(p => p === p), 1);
        }
    }

    await Promise.all(executing); // 等待剩余的任务完成
    return result;
}

9. Promise 中的 resolvereject 是什么?

  • resolve(value):表示 Promise 成功解决,并返回一个值(可以是任意类型,包括另一个 Promise)。

  • reject(reason):表示 Promise 失败,并返回一个拒绝原因(通常是一个错误对象)。

10. Promise 在 setTimeout() 中的应用是什么?

setTimeout() 会在指定的延迟后执行一个回调,而 Promise 可以用于更优雅的延迟操作。例如,将 setTimeout() 封装为一个返回 Promise 的函数:

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000).then(() => {
    console.log('1秒钟后执行');
});