手写Promise.all() 方法(前端面试真题)

文章目录

    • promise简介
    • 第一版
    • 第二版
    • 小结

promise简介

Promise 是JavaScript在ES6中引入的一种用于异步计算的对象。它代表了一个可能现在还没有结果,但将来某个时刻会有结果的值。Promise 的主要目的是提供一个更合理、更强大的方式来处理异步操作。

  1. 状态:一个 Promise 对象有三种状态:

    • Pending(等待中):初始状态,既不是成功,也不是失败状态。

    • Fulfilled(已成功):意味着操作成功完成。

    • Rejected(已失败):意味着操作失败。

  2. 创建:使用 new Promise 构造函数来创建一个新的 Promise 对象。

    let promise = new Promise((resolve, reject) => {
        if(/*条件成立*/) {
        resolve() // 调用成功方法,状态改变为fulfilled
        } else {
        reject() // 调用失败方法,状态改变为rejected
        }
    }

    promise构造函数接受一个回调函数作为参数,回到函数会被注入resolve与reject两个函数作为参数,分别用于改变promise对象的两种状态。

        值得注意的是,一但promise的状态由Pending切换为FulfilledRejected之一后,将无法再次改变。同时promise的状态只有通过其构造函数来改变,在promise外部无法修改其状态。

        3. 处理结果:使用 .then() 方法来添加在 Promise 成功时执行的回调函数,使用 .catch() 方法来添加在 Promise 失败时执行的回调函数。

promise .then(value => { 
// 当 promise 成功时,这里的代码会被执行 
console.log('Success:', value); 
}) .catch(error => { 
// 当 promise 失败时,这里的代码会被执行 
console.error('Error:', error);
});

        4.链式调用.then() 方法返回一个新的 Promise,这允许你链式调用多个 .then().catch()。

doSomething() 
.then(result => doSomethingElse(result)) 
.then(newResult => doThirdThing(newResult)) 
.catch(error => console.error(error));

        5. Promise.all():用于处理多个 Promise 对象,只有当所有 Promise 都成功时,它才会成功。如果任何一个 Promise 失败,它就会失败。

Promise.all([promise1, promise2, promise3])
 .then(([result1, result2, result3]) => { 
// 所有 Promise 都成功时执行 
}) .catch(error => { 
// 任何一个 Promise 失败时执行 
});

        6. Promise.race():与 Promise.all() 类似,但是只要有一个 Promise 成功或失败,它就会立即执行。

第一版

Promise.all = Promise.all || function (promises) {
    // 判断传入参数是否是数组
    if(!Array.isArray*(promises)) {
        throw new Error('Promise.all must accept an array'); // 如传入参数不是数组,抛出错误提示该函数必须传入一个数组作为参数
    }
    return new Promise((resolve,reject) => {
        // 结果数组
        const res = []
        // 遍历参数
        promises.forEach((item ,index)=> {
            // 判断每个item是否是promise对象
            if(!item instanceof Promise) {
                reject(new Error('Promise.all must accept an array of promises')) // 如果不是promise对象,则抛出错误提示需要接受promise对象类型的数据
            }
            // 执行该对象
            item.then(data => {
                res[index] = data  // 将执行结果存入结果数组中
                index++  // 序号增加
                // 若此时序号与传入参数长度一致,则说明已经遍历执行完所有的promise对象
                if(index === promises.length) {
                    resolve(res) // 返回结果数组
                } 
            },
            reason => {
                // 此时说明有promise对象执行失败,直接返回失败结果
                reject(reason)
            })
        })
    })
}

第二版

第二版中限制使用ES6相关的新特性,使用低版本实现。(面试官但还是限制使用const ,let,forEach等高级API)

Promise.all = Promise.all || function (promises) {
    // 判断传入参数是否是数组
    if(!Array.isArray(promises)) {
        throw new Error('Promise.all must accept an array'); // 如传入参数不是数组,抛出错误提示该函数必须传入一个数组作为参数
    }
    return new Promise((resolve,reject) => {
        // 结果数组
       var res = []
       for(var i = 0; i < promises.length; i++) {
            // 由于promise是异步执行的,所有必须将每次的i通过函数参数的形式传递给then方法否则promise还未执行,i已经增加了
            function handle(index) {
                // 判断每个元素是否是promise对象
                if(!promises[index] instanceof Promise) {
                    reject(new Error('Promise.all must accept an array of promises')) // 如果不是promise对象,则抛出错误提示需要接受promise对象类型的数据
                }
                promises[index].then(data => {
                    res[index] = data  // 将执行结果存入结果数组中
                    index++  // 序号增加
                    // 若此时序号与传入参数长度一致,则说明已经遍历执行完所有的promise对象
                    if(index === promises.length) {
                        resolve(res) // 返回结果数组
                    } 
                },
                reason => {
                    // 此时说明有promise对象执行失败,直接返回失败结果
                    reject(reason)
                })
            }
            handle(i)
       }
    })
}

小结

主要差异

  • 索引处理:方法二通过使用 handle 函数和 let 声明避免了闭包问题,确保每个 Promise 都能正确地更新自己的索引位置。
  • 错误处理:方法二在发现非 Promise 对象时立即拒绝,而方法一则可能在循环结束后才抛出错误。
  • 结果数组初始化:方法二使用 new Array(promises.length) 来初始化结果数组,这有助于避免索引问题。

尽管两种方法都尝试实现 Promise.all 的功能,但方法二在处理异步操作和索引管理方面更为健壮和准确。然而,需要注意的是,这些自定义实现通常不应该用于生产环境,因为原生的 Promise.all 方法已经非常优化,并且经过了广泛的测试。自定义实现可能会引入难以预料的bug,特别是在处理复杂的异步逻辑时。

你可能感兴趣的:(前端面试,前端,javascript,开发语言,面试)