力扣 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)这样的代码,我也不知道了,当初就是那样想的。
`错误四` :result1
和 result2
是异步拿到的,在执行 resolve
的时候可能还没取到result1,result2。
版本二(失败)
/*** @param {Promise} promise1* @param {Promise} promise2* @return {Promise}*/
var addTwoPromises = async function (promise1, promise2) {return new Promise((resolve, reject) => {let runCount = 2promise1.then((res) => {const result1 = resif (--runCount == 0)resolve(result1 + result2)})promise2.then((res) => {const result2 = resif (--runCount == 0)resolve(result1 + result2)})})};/*** addTwoPromises(Promise.resolve(2), Promise.resolve(2))* .then(console.log); // 4*/
报错
报错原因:const result1 = res
和 const 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,result2promise1.then((res) => {result1 = resif (--runCount == 0)resolve(result1 + result2)})promise2.then((res) => {result2 = resif (--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。这些方法有助于扁平化代码结构,使其更可读和可维护,避免过多的回调嵌套。