谈一谈Promise

Promise

引言:谈谈你对Promise的理解?

为什么会出现Promise?
promise的api有哪些?
手写Promise
Promise有哪些缺陷?

为什么和什么?

为什么出现:在promise出现之前,需要进行连续多个异步操作任务,每个任务都依赖于前一个操作任务完成后才能执行,这个时候是通过连续的回调函数嵌套完成的。这个时候代码就变得难以阅读和维护,这种情况为回调地狱问题。回调地狱问题例子:

// 多个回调函数嵌套,导致结构很不清晰。
// 假设有三个异步操作:获取用户数据、获取用户帖子数据、获取帖子评论数据  
function getUser(userId, callback) {  
  setTimeout(() => {  
      console.log("Fetching user...");  
      const user = { id: userId, name: "John Doe" };  
      callback(null, user);  
  }, 1000);  
}  

function getPosts(userId, callback) {  
  setTimeout(() => {  
      console.log("Fetching posts...");  
      const posts = [{ id: 1, content: "Post 1" }, { id: 2, content: "Post 2" }];  
      callback(null, posts);  
  }, 1000);  
}  

function getComments(postId, callback) {  
  setTimeout(() => {  
      console.log("Fetching comments...");  
      const comments = [{ id: 1, content: "Comment 1" }, { id: 2, content: "Comment 2" }];  
      callback(null, comments);  
  }, 1000);  
}  

// 回调函数地狱示例:一层嵌套一层
getUser(1, (err, user) => {  
  if (err) {  
      console.error("Error fetching user:", err);  
      return;  
  }  
  getPosts(user.id, (err, posts) => {  
      if (err) {  
          console.error("Error fetching posts:", err);  
          return;  
      }  
      posts.forEach(post => {  
          getComments(post.id, (err, comments) => {  
              if (err) {  
                  console.error("Error fetching comments:", err);  
                  return;  
              }  
              console.log(`Post ${post.id} comments:`, comments);  
          });  
      });  
  });  
});

什么是:Promise是异步编程的一种解决办法,用来获取异步操作结果值的对象。它的出现可以避免回调地狱问题,Promise的出现使得获取异步任务结果的方案变得更合理更强大。promise共有三种状态:Pending、Resolved、Rejected。当把异步任务放在Promise中处理,它的状态就是Pending,任务完成后并且成功变成Resolved,失败了就是Rejected。状态一旦改变了就不会再更改了!

// Promise解决回调地狱问题
function getUser(userId){
  return new Promise((resolve, reject) => {
    // 这里的异步操作用定时器代替
    setTimeout(() => {
      const user = {id : userId, name :"John"}
      resolve(user)
    },1000)
  })
}
function getPosts(postId){
  return new Promise((resolve, reject) => {
    // 这里的异步操作用定时器代替
    setTimeout(() => {
      const posts = {id : postId, name :"John"}
      resolve(posts)
    },1000)
  })
}
function getComments(postId){
  return new Promise((resolve, reject) => {
    // 这里的异步操作用定时器代替
    setTimeout(() => {
      const comments = {id : postId, name :"John"}
      resolve(comments)
    },1000)
  })
}

// 这里再调用异步任务函数就不会出现上面的回调地狱情况
getUser(userId).then(user => 
  getPosts(user.id)).then(posts => {
    return Promise.all(posts.map(post => getComments(post.id)))
  }).then(comments => {
    comments.forEach((comment, index) => {
      console.log(comment);
    })
  }).catch(err => {
    console.log(err);
  })

api有哪些?

Promise.resolve

Promise.reject

Promise.all

Promise.race

Promise.allSettled

Promise.any

promise.try

Promise.prototype.catch

Promise.prototype.finally

Promise.prototype.then

手写Promise

  • Promise是一个构造函数,是异步编程的一种解决方案,通过Promise对象我们可以获取异步操作的消息。而
  • Promise最主要的特性就是:链式调用状态转换错误处理

手写实现的Promise及其then()和catch()方法的简化版

class MyPromise {
  constructor(executor) {
    this.state = 'pending'
    this.value = undefined      // 临时保存 resolve 中的参数
    this.reason = undefined     // 临时保存 reject 中的参数
    this.onFulfilledCallbacks = []    // 装 then 中的回调
    this.onRejectedCallbacks = []     // 装 catch 中的回调
    
    const resolve = (value) => {
      // 判断状态,保证 resolve,reject 只会执行一个状态
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        // cb 代表数组里面的每一项
        this.onFulfilledCallbacks.forEach(cb => cb(value))
      }
    }
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = reason
        // cb 代表数组里面的每一项
        this.onRejectedCallbacks.forEach(cb => cb(reason))
      }
    }
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  // then方法并且要返回一个 Promise(),这样才能实现接多个 .then 的效果
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    return new MyPromise((resolve, reject) => {
      const fulfilledCallback = () => {
        try {
          const result = onFulfilled(this.value);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      const rejectedCallback = () => {
        try {
          const result = onRejected(this.reason);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      if (this.state === 'fulfilled') {
        setTimeout(fulfilledCallback, 0);
      } else if (this.state === 'rejected') {
        setTimeout(rejectedCallback, 0);
      } else if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(fulfilledCallback);
        this.onRejectedCallbacks.push(rejectedCallback);
      }
    });
  }
  // catch方法相当于then方法语法糖
  catch(onRejected){
    return this.then(null, onRejected)
  }
}
// 使用示例
const p1 = new MyPromise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    // resolve('成功');
    reject('失败')
  }, 1000);
})
p1.then(res => {
  console.log(res);   // 输出 "成功"
}).catch(err => {
  console.log(err);
})

手写Promise.all

function myPromiseAll(promisesArr){
  return new Promise((resolve, reject) => {
    // 判断任务列队是否是数组不是的话 抛出错误
    if(!Array.isArray(promisesArr)){
      reject(new TypeError(`${promisesArr} must be an array`))
    }
    let promisesCount = 0
    let results = []
    promisesArr.forEach((p, index) => {
        Promise.resolve(p).then(res => {
          results[index] = res
          promisesCount++

          if(promisesCount === promisesArr.length){
            resolve(results)
          }
        }, err => {
          reject(err)
        })
    });
  })
}

let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => resolve(('成功')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
  setTimeout(() => {
    resolve(4)
  },4000)
})
myPromiseAll([a,b,c,d,e]).then(
  res => {console.log(res);  // [ 1, 2, '成功', 3, 4 ]
  }).catch(err => {
    console.log(err); // 出错了
  }) // [1, 2, 3]

手写Promise.race

function myPromiseRace(promisesArr){
  return new Promise((resolve, reject) => {
    if(!Array.isArray(promisesArr)){
      reject(new TypeError(`${promisesArr} is must an array!`))
    }
    promisesArr.forEach(p => {
       // 对每个 Promise 进行处理 
       // 这里用 Promise.resolve()包装一下p是考虑promises中的值有可能不是promise对象 
      Promise.resolve(p).then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    })
  })
  }

 /*  myPromiseRace(a,b).then(res => {
    console.log(res);
  }).catch(err => {
    console.log(err);
  }) // [object Promise] is must an array! */

  myPromiseRace([c,d]).then(res => {
    console.log(res); //成功
  }).catch(err => {
    console.log(err);
  }) 

Promise.all和Promise.allSettled的区别

Promise.all的广泛使用中,一旦出现reject的情况下,promise.all就停止了其他请求,这在某些情况下有不适合业务场景了,于是Promise的工具包里又多了一个方法Promise.allSettled,是对Promise.all的一种补充,缓解了使用Promise.all碰到reject的痛点问题。

一句话概括Promise.allSettled和Promise.all的最大不同:Promise.allSettled永远不会被reject

当需要处理多个Promise并行时,大多数情况下Promise.all用起来好使的,比如下面这样

const delay = (n) => new Promise((resolve) => setTimeout(resolve, n))
const promises = [delay(100).then(() => 1), delay(200).then(() => 2)]
Promise.all(promises).then((values) => console.log(values))
// 最终输出: [1, 2]

可是,是一旦有一个promise出现了异常,被reject了,情况就会变的麻烦。

const promises = [
  delay(100).then(() => 1),
  delay(200).then(() => 2),
  Promise.reject(3),
]
Promise.all(promises).then((values) => console.log(values))
// 最终输出: Uncaught (in promise) 3
Promise.all(promises)
  .then((values) => console.log(values))
  .catch((err) => console.log(err))
// 加入catch语句后,最终输出:3

Promise.all能用catch捕获其中的异常,但其他执行成功的Promise的结果不会返回。要么全部成功,要么全部重来,这是Promise.all本身的强硬逻辑,也是痛点的来源,但这的确给Promise.allSettled留下了立足的空间。

const promises = [
  delay(100).then(() => 1),
  delay(200).then(() => 2),
  Promise.reject(3),
]
Promise.allSettled(promises).then((values) => console.log(values))
// 最终输出:
//    [
//      {status: "fulfilled", value: 1},
//      {status: "fulfilled", value: 2},
//      {status: "rejected", value: 3},
//    ]

当用Promise.allSettled时,我们只需专注在then语句里,当有promise被异常打断时,我们依然能妥善处理那些已经成功了的promise,不必全部重来。

手写Promise.allSettled

function myPromiseAllSettled(promisesArr){
  return new Promise((resolve, reject) => {
    if(!Array.isArray(promisesArr)){
      reject(new TypeError(`${promisesArr} must be an array`))
    }
    let results = []
    let cnt = 0
    promisesArr.forEach((p,i) => {
      Promise.resolve(p).then(res => {
        results[i] = {status:"fulfilled", value: res}
        cnt++
        if(cnt === promisesArr.length){
          resolve(results)
        }
      }, err => {
        results[i] = {status: "rejected", reason: err}
        cnt++
        if(cnt === promisesArr.length){
          resolve(results)
        }
      })
    })

  })
}
let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => reject(('失败')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
  setTimeout(() => {
    resolve(4)
  },4000)
})

myPromiseAllSettled([a,b,c,d,e]).then(res => {
  console.log(res);
}) // [  { status: 'fulfilled', value: 1 },  { status: 'fulfilled', value: 2 },  { status: 'rejected', reason: '失败' },  { status: 'fulfilled', value: 3 },  { status: 'fulfilled', value: 4 }]

手写Promise.any

  • 作用:从多个 Promise 中返回第一个成功(resolve)的 Promise 结果或者等到所有状态变成rejected状态,结果值才会变成rejected状态。
  • 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
  • 使用场景:当你需要多个异步操作中的任意一个成功时使用,比如加载多个资源只需一个成功即可继续。
function myPromiseAny(promisesArr){
  return new Promise((resolve, reject) => {
    if(!Array.isArray(promisesArr)){
      reject(new TypeError(`${promisesArr} must be an array`))
    }
    let result = []
    let cnt = 0
    promisesArr.forEach((p,i) => {
      Promise.resolve(p).then((res) => {
        resolve(res)
      }, err => {
        result[i] = err
        cnt++
        if(cnt === promisesArr.length){
          reject(new AggregateError(result, 'All promises were rejeted'))
        }
      })
    })
  })
}
let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => reject(('失败')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
  setTimeout(() => {
    resolve(4)
  },4000)
})
let f = new Promise((resolve,reject) => reject(('失败')))
myPromiseAny([a,b,c]).then(res => {
  console.log(res); // 1
})
myPromiseAny([c,f]).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err); // [errors]: [ '失败', '失败' ]
})

Promise有哪些缺陷?

  • Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担。
  • Promise传递中间值非常麻烦,Promise的错误捕获非常冗余。
  • Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

几个需要注意的点

异常穿透

在 Promise 的链式调用链中,当所有的 .then 中都没有指定错误处理的回调,则前面出现的异常会在最后失败的回调中处理。

new Promise((resolve, reject) => {
    reject(1)
 }).then(
     value => {
        console.log(value)
     }).then(
       value => {
        console.log(value)
     }).catch(reason => {
          console.log('异常:' + reason)
     })

注意:

  • .catch 所谓的异常穿透并不是一次失败状态就触发 catch,而是一层一层的传递下来的

  • 异常穿透的前提条件是所有的 .then 都没有指定失败状态的回调函数。

  • 如果 .catch 前的所有 .then 都指定了失败状态的回调函数,.catch 就失去了意义。

中断 Promise 链

  • 什么是中断:当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
  • 中断的方法:在回调函数中返回一个 pending 状态的 promise 对象
如何返回一个 pendding 状态的 promise 对象:
1. return new Promise(() => { }) // pending
2. return Promise.race([]) // pending Promise.race([]),race 方法接收一个空的可迭代对象时,该 Promise 会一直处于 pendding 状态。
// 中断之前:
Promise.resolve(1)
    .then(value => {
    console.log(value) // 1
 }) // 下面都会打印
.then(value => console.log(value)) // undefined: 因为上面的then回调没有返回值
.then(value => console.log(value)) // undefined
// 中断之后:

Promise.resolve(1).then(value => {
    console.log(value) // 1
    // 返回一个 pending 状态的 promise 来中断这个调用链
    return new Promise(() => { })
    // 或者:return Promise.race([])
  }) // 下面都不会打印
 .then(value => console.log(value))
 .then(value => console.log(value))

promise.then() 返回的新promise 的结果状态由什么决定?

简单表达:由 then() 指定的回调函数执行的结果决定

详细表达:

  • 如果抛出异常,新 promise 变为 rejected, reason为抛出的异常

  • 如果返回的是非 promise 的任意值,新promise变为 resolved,value 为返回的值

  • 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果

改变 promise 状态和指定回调函数谁先谁后?

都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调

如何先改状态再指定回调?(在执行器中直接调用resolve()/reject())

  • 在执行器中直接调用 resolve()/reject():立即 resolve() 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

  • 延迟更长时间才调用 then()

什么时候才能得到数据?

  • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据

  • 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据

// 正常情况,先指定回调再改变状态
const p2 = new Promise((resolve, reject) => {
// setTimeout 第三个参数,该参数将传入第一个函数参数
// 异步任务完成后,改变状态
    setTimeout(resolve, 1000, '成功的数据') 
        // 后改变数据,同时传入值
    }).then(
        // 先指定回调函数,保存当前回调函数
        value => {
            console.log(value)})

// 先改状态,在指定回调函数
  const p1 = new Promise((resolve, reject) => {
      resolve(100) 
   // 先改变数据,同时传入值}).then(
   // 后指定回调函数,异步执行回调函数
 value => {
    console.log(value)})

实现异步并发控制

题目:如果有100个请求甚至更多的请求,如何控制并发请求?(在同一时间,并发实现多个请求)

// 模拟请求列表
const requestList = [];
for(let i = 0; i < 100; i++){
  requestList.push(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('done', i);
        resolve(i)
      }, Math.random() * 1000)
    })
  })
}

Promise.all

/* 方式一:全部并发:Promise all */
const parallelRun = async max => {
  const requestSliceList = [];
  for (let i = 0; i < requestList.length; i += max) {
    requestSliceList.push(requestList.slice(i, i + max));
  }

  for (let i = 0; i < requestSliceList.length; i++) {
    const group = requestSliceList[i];
    try {
      const res = await Promise.all(group.map(fn => fn()));
      console.log('接口返回值为:', res);
    } catch (error) {
      console.error(error);
    }
  }
};

但是如果某个任务出错了,对于Promise.all 就会停止后续任务,直接返回错误任务的结果,显然是不对的。一组中一个请求失败就无法获取改组其他成员的返回值,这对于不需要判断返回值的情况倒是可以,但是实际业务中,返回值是一个很重要的数据, 我们可以接受某个接口失败了没有返回值,但是无法接受一个请求失败了,跟它同组的其他 9 个请求也没有返回值。

for(let i = 0; i < 100; i++){
  requestList.push(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (i === 42) {
          // 出错了,这次请求没有返回值
          reject(new Error('出错了,出错请求:' + i));
        } else {
          console.log('done', i);
          resolve(i);
        }
      }, Math.random() * 1000)
    })
  })
}

结果如下所示:可以看到当某一个任务出错后,只会返回错误任务的结果,停止后续的任务。

promise.all

Promise.allSettled()

先来看下权威的 MDN 的介绍

Promise.allSettled() 方法是 promise 并发方法之一。在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled()

简单说就是:每个请求都会返回结果,不管失败还是成功
使用 Promise.allSettled()替换下 Promise.all()

const parallelRun = async max => {
  const requestSliceList = [];
  for (let i = 0; i < requestList.length; i += max) {
    requestSliceList.push(requestList.slice(i, i + max));
  }

  for (let i = 0; i < requestSliceList.length; i++) {
    const group = requestSliceList[i];
    try {
      const res = await Promise.allSettled(group.map(fn => fn()));
      console.log('接口返回值为:', res);
    } catch (error) {
      console.error(error);
    }
  }
};

结果如下所示:每个请求都会返回结果,不管失败还是成功。

promise.allSettled

接口全部正常有返回值,返回值中会正常记录当前请求时成功还是失败。

那如果有一个请求非常耗时,会出现什么情况?
答:有一个请求非常耗时,那组的请求返回就会很慢,会阻塞了后续的接口并发
有没有什么方法可以解决这个问题?

限制并发处理

可以维护一个运行池和一个等待队列,运行池始终保持 10 个请求并发,当运行池中有一个请求完成时,就从等待队列中拿出一个新请求放到运行池中运行,这样就可以保持运行池始终是满负荷运行,即使有一个慢接口,也不会阻塞后续的接口入池

最优解

实现一

利用Promise实现并发控制请求,设置并发的最大数量,当第一个任务完成,立马在放入新任务,知道所有任务完成!!!

代码:

/* 首页有10个并发请求, 先发送3个, 3个中哪一个响应了, 立即发送第4个, 直到第10个发送完成 */
function limitLoad(urls, handler, limit) {
  const sequence = [].concat(urls)
  let promise = []
  // splice改变原数组的值
  promise = sequence.splice(0, limit).map((url, index) => {
      return handler(url).then(()=>{
          return index
      })
  })
  // p是第一个改变状态的promise对象
  let p = Promise.race(promise)
  // for循环给p赋值相当于.then().then()链式调用
  for (let i = 0; i < sequence.length; i++) {
      // p.then((res) => {console.log('this',res);});
      p = p.then(res => {
        // console.log(res, '这里的res是什么?--> 当前处理的任务在promise数组中对应的下标');
          // 处理剩下的任务
          promise[res] = handler(sequence[i]).then(()=>{
            // console.log(res,'res是啥?---> 当前处理任务对应的下标值');
              return res
          })
          return Promise.race(promise)
      })
  }
}

function loadImg(url){
  return new Promise((reslove, reject)=>{
    // 异步任务开始执行
      console.log(url.info + '---start')
      setTimeout(()=>{
          //异步任务完成 
          console.log(url.info, 'ok!!!')
          reslove()
      }, url.time)
  })
}

const urls =[
  {info:'1', time:2000},
  {info:'2', time:1000},
  {info:'3', time:2000},
  {info:'4', time:2000},
  {info:'5', time:3000},
  {info:'6', time:1000},
  {info:'7', time:2000},
  {info:'8', time:2000},
  {info:'9', time:3000},
  {info:'10', time:1000},
  {info:'11', time:1000},
  {info:'12', time:1000},
  {info:'13', time:1000}
]
limitLoad(urls, loadImg, 4)

实现二

最优解:初始化一个运行池和任务队列,每当池子完成一个任务,从任务队列中拿出一个任务扔到池子里面,直到所有任务处理完毕 。

// 1.初始化运行池和任务等待队列
const pool = new Set()
const waitQueue = []
/**
 * @description: 限制并发数量的请求
 * @param {*} reqFn :请求方法
 * @param {*} max :最大并发数
 */
const request = (reqFn, max) => {
  return new Promise((resolve, reject) => {
    // 判断运行池是否已满
    const isFull = pool.size >= max
    // 包装新的请求
    const newReqFn = () => {
      // reqFn是返回Promise对象的函数(模拟的异步任务)
      reqFn().then(res => resolve(res))
      .catch(err => {
        reject(err)
      }).finally(() => {
        // 请求完成后,将该请求从运行池中删除
        pool.delete(newReqFn)
        const next = waitQueue.shift()
        if(next){
          pool.add(next)
          next()
        }
      })
    }
    // 判断运行池是否已满,不满的话,运行池添加新任务,并执行新任务
    if(isFull){
      waitQueue.push(newReqFn)
    }else{
      pool.add(newReqFn)
      newReqFn()
    }
  })
}

requestList.forEach(async item => {
  const res = await request(item, 10)
  console.log('res结果是:',res);
})

结果如下所示:可以看到每个任务都会正常执行,不会造成阻塞:

最优解

引入库 p-limit

安装:npm install p-limit -S

/* 方式四:使用库 */
import plimit from 'p-limit';
const limit = plimit(10);
requestList.forEach(async item => {
  const res = await limit(item);
  console.log(res);
});

async和await

async和await相当于是对generator函数的改进:

  • 内置执行器
  • 更好的语义
  • 返回值是promise

async和await就是将generator函数转换成了Promise对象。async(相当于*)就是generator函数的语法糖,await(相当于yield)就是内部then命令的语法糖。async修饰的函数return的返回值,就是then回调函数的参数

async 和 Promise对象的区别

  • async是一种语法,promise是一个内置对象。
  • async函数本质上可以看做是多个异步任务操作包装成的Promise对象
  • async函数在处理多个异步串行请求时更方便
  • async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

给定一个 URL 数组,如何实现接口的继发和并发

async 继发实现

// 继发一
async function loadData(){
  const res1 = await fetch(url1)
  const res2 = await fetch(url2)
  const res3 = await fetch(url3)
  return 'all done!'
}

// 继发二
async function loadData1(urls){
  for(let url of urls){                    
    const res = await fetch(url)
    console.log(await res.text());
  }
}

async 并发实现

// 并发一
async function loadData2(){
  const res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)])
  return 'all done'
}

// 并发二
async function loadData3(url3){
  // 并发读取url
  const resArr = url3.map(async url => {
    const res = await fetch(url)
    return res
  })

  // 输出结果
  for(const res of resArr){
    console.log(await res);
  }
}

try/catch 捕获多个错误并做不同的处理时,如何优化?

try catch 捕获错误,但是当我们需要捕获多个错误并做不同的处理时,很多 try catch 就会导致代码杂乱。为了简化这种错误的捕获,我们可以给 await 后的 promise 对象添加 catch 函数。

export default function handler(promise){
  // 捕获Promise对象的结果值进行处理
  return promise.then(data => {
    return [null, data]
  }).catch(err => [err])
}

async/await对比Promise的优势

  • async/await代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担
  • Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
  • 错误处理友好,async/await可以用成熟的try/catch,Promise的错误捕获非常冗余
  • 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值