力扣 30 天 JavaScript 挑战 第41天 (第十二题)对异步操作,promise,async/await有了更深理解

开始答题

版本一(失败)

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
    return now Promise((resolve,reject)=>{
        promise1.then(const result1 = result)
        promise2.then(const result2 = result)
        resolve(result1+result2)
    })
    
};

/**
 * addTwoPromises(Promise.resolve(2), Promise.resolve(2))
 *   .then(console.log); // 4
 */

提交报错

询问ai报错原因

错误一:是new Promise 不是now Promise

错误二promise1.then(const result1 = result) 语法错误,.then() 里面应该传函数,不能直接写赋值。

解决这个错误的时候,我发现函数,表达式,语句的区别我不太懂,附上一张图解释它们的区别

错误三result 没定义,你要的是 .then(res => {...})

会犯这个错误的原因是,我之前看是有这样的写法

复制代码
promise const p1 = new Promise(resolve => setTimeout(() => resolve("A"), 1000)); 
p1.then(console.log);
//p1.then(console.log);等价于p1.then((res)=>console.log(res))

为什么想起上述的写法,我会写出 promise1.then(const result1 = result)这样的代码,我也不知道了,当初就是那样想的。

`错误四` :result1result2 是异步拿到的,在执行 resolve 的时候可能还没取到result1,result2。

版本二(失败)

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function (promise1, promise2) {
    return new Promise((resolve, reject) => {
        let runCount = 2
        promise1.then((res) => {
            const result1 = res
            if (--runCount == 0)
                resolve(result1 + result2)
        })
        promise2.then((res) => {
            const result2 = res
            if (--runCount == 0)
                resolve(result1 + result2)
        })

    })

};

/**
 * addTwoPromises(Promise.resolve(2), Promise.resolve(2))
 *   .then(console.log); // 4
 */

报错

报错原因:const result1 = resconst result2 = res 都是 块级作用域变量const 定义的变量只在当前函数块里可见)。

我改的时候,把变量提升搞乱了,我尝试使用了var来声明变量,以为使用var声明的变量提升了,promise1.then里面就可以拿到result2的值了,结果是拿不到。

原因是:

var 变量提升,只在函数作用域 里有效。我的两个 .then 各自是一个独立函数作用域,互相看不到对方的变量。

版本三(成功)

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function (promise1, promise2) {
    return new Promise((resolve, reject) => {
        let runCount = 2,result1,result2
        promise1.then((res) => {
            result1 = res
            if (--runCount == 0)
                resolve(result1 + result2)
        })
        promise2.then((res) => {
            result2 = res
            if (--runCount == 0)
                resolve(result1 + result2)
        })

    })

};

/**
 * addTwoPromises(Promise.resolve(2), Promise.resolve(2))
 *   .then(console.log); // 4
 */

学习官方题解里面的知识点

什么是 Promise?

promise表示现在还没有完成,将来会完成的一个异步操作。

它有三种状态

1.挂起:初始状态,还没有完成

2.已完成 :异步操作成功了,得到了值,成功的操作通过resolve返回值,在.then中拿到通过resolve返回的值。

3.已拒绝 :异步操作失败了,发生了错误,错误的操作通过reject返回值,在.catch中拿到reject返回的错误。

什么是async/await

async/await是promise的语法糖,通过这个语法糖可以像处理同步操作那样处理异步函数。

async

  • 放在函数前面,表示这个函数一定会返回一个promise。
  • 即使函数返回的是一个普通的值,加了async也会变成promise。

await

  • 只能在async函数里面使用
  • await会暂停当前函数,等待promise函数执行结束,才会继续执行函数
  • 如果 Promise 成功,返回结果继续往下执行;如果失败,就抛出异常。

Promise.all() 是干嘛的?

有时候你需要 并行执行多个异步任务 ,并在它们全部完成后再继续。

这时就可以用 Promise.all()

  • 传进去一个数组(里面可以是 Promise,也可以是普通值)。
  • 它会返回一个新的 Promise:
    • 如果所有任务都成功,返回的 Promise 也成功,结果是一个「数组」,包含每个 Promise 的结果,顺序和输入一致。
    • 如果有一个任务失败,返回的 Promise 就直接失败,失败原因就是那个 Promise 抛出的错误。

举例

✅ 所有任务都成功

复制代码
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
  .then(values => console.log(values)); 
// 输出: [1, 2, 3]

❌ 有任务失败

复制代码
Promise.all([Promise.resolve(1), Promise.reject("error"), Promise.resolve(3)])
  .then(values => console.log(values))
  .catch(err => console.log(err));
// 输出: "error" 这里只会输出一个error 是在catch里输出的,如果失败了就不走then了

实际应用场景

假设你要同时请求三个 API:

复制代码
const user = fetch('/api/user');
const posts = fetch('/api/posts');
const comments = fetch('/api/comments');

Promise.all([user, posts, comments])
  .then(([userRes, postsRes, commentsRes]) => {
    // 三个请求都完成后,才会到这里
    console.log("全部完成:", userRes, postsRes, commentsRes);
  })
  .catch(err => console.error("有一个请求失败:", err));

开始学习官方的解法

解法一:使用promise.all

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
  try {
    const [res1, res2] = await Promise.all([promise1, promise2]);
    return res1 + res2;
  } catch (error) {
    console.error(error);
    throw error; // 重新抛出错误以保持将错误传播给调用者的行为
  }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/add-two-promises/solutions/2506145/shi-yong-promise-chu-li-yi-bu-cao-zuo-by-m5ob/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法二:不使用promise.all

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
  try {
    return await promise1 + await promise2;
  } catch (error) {
    console.error(error);
    throw error; // 重新抛出错误以保持将错误传播给调用者的行为
  }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/add-two-promises/solutions/2506145/shi-yong-promise-chu-li-yi-bu-cao-zuo-by-m5ob/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法三:Promise.then 链式调用

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function(promise1, promise2) {
  try{
     return promise1.then(
       (val1)=>promise2.then((val2)=>val1+val2)
    )
  } catch(error){
    throw error
  }
};

/**
 * addTwoPromises(Promise.resolve(2), Promise.resolve(2))
 *   .then(console.log); // 4
 */

这里要注意 不能写成promise2.then((val2)=>{val1+val2})

因为

  • 箭头函数 没花括号 = 自动返回表达式。
  • 箭头函数 有花括号 = 需要手写 return

解法四:使用计数器

复制代码
/**
 * @param {Promise} promise1
 * @param {Promise} promise2
 * @return {Promise}
 */
var addTwoPromises = async function (promise1, promise2) {
  return new Promise((resolve, reject) => {


    let count = 2;
    let res = 0; 

    [promise1, promise2].forEach(async promise => {
      try {
        const subRes = await promise;
        res += subRes;
        count--;

        if (count === 0) {
          resolve(res);
        }
      } catch (err) {
        reject(err);
      }
    });
  });
};

涉及到的面试题

1. 说一下promise.all

答案见上述

2. 如何处理promise的错误

通过try catch finally

3. 同步,异步的区别

同步是指一个程序执行完后,另一个程序再执行。异步是指多个程序可以同时执行。

4. 回调函数和promise有什么区别,为什么更喜欢用promise,回调地狱是什么,如何解决回调地狱。

回调函数是将一个函数作为参数传递给另一个函数,当异步操作完成是调用这个作为参数的函数

示例:

复制代码
function getData(callback) {
  setTimeout(() => {
    callback("数据加载完成");
  }, 1000);
}

getData((result) => {
  console.log(result); // 1 秒后输出 "数据加载完成"
});

👉 缺点:

  • 回调函数之间会不断嵌套,逻辑复杂时非常难读。
  • 错误处理不统一,每层都要自己处理 error

Promise 是一个对象,表示 异步操作的最终结果 (成功或失败)。

它提供 .then().catch() 来处理结果,更清晰地链式调用。

示例:

复制代码
function getData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("数据加载完成");
    }, 1000);
  });
}

getData().then((result) => {
  console.log(result); // 1 秒后输出 "数据加载完成"
});

👉 优点:

  • 可以链式调用:p.then(...).then(...).catch(...)
  • 错误捕获统一用 .catch,比回调函数更干净。

回调地狱是指代码结构变得嵌套层次很深,每个回调都作为另一个回调的参数传递。这种嵌套会很快变得复杂,使代码难以理解,导致问题,如代码重复、错误处理问题以及难以维护和调试的困难。

为了缓解回调地狱,可以使用几种方法,例如使用命名函数、使用控制流库(如 async.js 或 Promises)或使用现代 JavaScript 特性如 async/await。这些方法有助于扁平化代码结构,使其更可读和可维护,避免过多的回调嵌套。