前端必刷手写题系列 [16]

408 阅读6分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。

平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态习惯思路清晰程度等。

注意是简单实现,不是完整实现,重要的是概念清晰实现思路清晰,建议先解释清除概念 => 写用例 => 写伪代码 => 再实现具体功能,再优化,一步步来。

26. 按顺序调用异步函数

问题是什么

我们在开发过程中经常遇到一个页面需要调用几个不同接口,需要按顺序的那种。举个例子:

// 先建立一个工厂函数, 入参是名字和异步时间
const promiseFactory = (name, wait, isFail = false) => {
  return new Promise((resolve, reject) => {
    // 异步任务(用 setTimeout 模拟)
    setTimeout(() => {
      if (!isFail) {
        resolve(`我是 ${name},我需要 ${wait} ms, 执行成功`)
      } else {
        reject(`我是 ${name},我需要 ${wait} ms, 执行失败`)
      }
    }, wait)
  })
}

let pro1 = promiseFactory('第一个异步任务', 3000)
let pro2 = promiseFactory('第二个异步任务', 1000)
let pro3 = promiseFactory('第三个异步任务', 2000)
let pro4 = promiseFactory('第四个异步任务', 1500, true)

pro1.then(res => console.log(res))
pro2.then(res => console.log(res))
pro3.then(res => console.log(res))
pro4.catch(err => console.log(err))

打印结果是

  • 我是 第二个异步任务,我需要 1000 ms, 执行成功 (在1000 ms 后打印)
  • 我是 第四个异步任务,我需要 1500 ms, 执行失败 (在1500 ms 后打印)
  • 我是 第三个异步任务,我需要 2000 ms, 执行成功 (在2000 ms 后打印)
  • 我是 第一个异步任务,我需要 3000 ms, 执行成功 (在3000 ms 后打印)

我们可以看出,这种写法我们其实是按照成功或者失败的返回时间排序和执行打印的。

分析

那么我们如何按照名称,或者调用顺序来执行呢

Promise.all

我们也许会想到 Promise.all()

先简单介绍下 Promise.all()方法

  • 接收一个一个可迭代的对象,例如Array,其中每个成员理论上都应该是Promise,数组中如有非Promise项,则此项当做成功
  • 如果所有Promise都成功,则返回成功结果数组
  • 如果有一个Promise失败,则返回这个失败结果

我们来试试,注意我的参数顺序

Promise.all([pro1, pro3, pro2, 99]).then(res => {
  console.log(res) 
}, err => {
  console.log(err)
})

这是都成功的情况, 返回成功结果数组

3 秒后输出

[ '我是 第一个异步任务,我需要 3000 ms',  '我是 第二个异步任务,我需要 1000 ms',  '我是 第三个异步任务,我需要 2000 ms',  99 ]

最后一个是非Promise项,则此项当做成功, 直接返回

如果有一个Promise失败,则返回这个失败结果

Promise.all([pro2, pro3, pro4]).then(res => {
  console.log(res) 
}, err => {
  console.log(err)
})
// 我是 第三个异步任务,我需要 1500 ms, 执行失败

那么又有人说,错误正确想一起返回

Promise.allSettled

我们看下 Promise.allSettled()

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。看下面例子

Promise.allSettled([pro2, pro1, pro3, pro4]).then(res => {
  console.log(res) 
}, err => {
  console.log(err)
})

// 打印结果
[ 
  {status: "fulfilled", value: "我是 第二个异步任务,我需要 1000 ms, 执行成功"},
  {status: "fulfilled", value: "我是 第一个异步任务,我需要 3000 ms, 执行成功"},
  {status: "fulfilled", value: "我是 第三个异步任务,我需要 2000 ms, 执行成功"},
  {status: "rejected", reason: "我是 第四个异步任务,我需要 1500 ms, 执行失败"} 
]

打印结果可以看出,不管成功失败,都会返回

async/await

其实我们还会想到用 async/await 看上去像同步的方式写异步,也不是不行

async function asyncList () {
  const res1 = await promiseFactory('第一个异步任务', 3000)
  console.log(res1)
  const res2 = await promiseFactory('第二个异步任务', 1000)
  console.log(res2)
  const res3 = await promiseFactory('第三个异步任务', 2000)
  console.log(res3)
}

asyncList()

3 秒后打印:

我是 第一个异步任务,我需要 3000 ms, 执行成功
再过 1 秒打印:
我是 第二个异步任务,我需要 1000 ms, 执行成功
再过 2 秒打印:
我是 第三个异步任务,我需要 2000 ms, 执行成功

这么写太麻烦了把,如果异步任务非常多,想用循环怎么办?

forEach

那么如果我们试着用 forEach 会发生什么呢

let proArr = [pro1, pro2, pro3]
proArr.forEach(async function(item) {
  let res = await item
  console.log(res)
})
  • 1s 后打印: 我是 第二个异步任务,我需要 1000 ms, 执行成功
  • 2s 后打印: 我是 第三个异步任务,我需要 2000 ms, 执行成功
  • 3s 后打印: 我是 第一个异步任务,我需要 3000 ms, 执行成功

这个其实主要是因为 forEach 的并发执行并不是我们想象的依次迭代,所以这3个 async/await 异步是独立的,按照回调的时间来打印。

for await of

我们还找到 ES9 中 有个 for await of

async function asyncFnList () {
  const proArr = [pro1, pro2, pro3]
  for await (let res of proArr) {
    console.log(res)
  }
}

asyncFnList()

3s 后同时输出

  • 我是 第一个异步任务,我需要 3000 ms, 执行成功
  • 我是 第二个异步任务,我需要 1000 ms, 执行成功
  • 我是 第三个异步任务,我需要 2000 ms, 执行成功

这样也行吧。

另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考