JS嵌套ajax+setInterval,怎样改成同步请求?

怎样让下面这段代码变成同步,顺序的打印出1 0 0 0 2 0 0 0 3 0 0 0 4 5

func() {
  console.log(1)
  const arr = [2, 3, 4]
  arr.forEach((num, idx) => {
    this.$http.get('a-url').then(() => {
      const interval = setInterval(() => {
        let i = 0
        this.$http.get('b-url').then(() => {
          i = i + 1
          console.log(0)
          if(i === 3) {
            clearInterval(interval)
          }
        })
      }, 1000)
    })
    console.log(num)
  })
  console.log(5)
}
阅读 2k
2 个回答
async func() {
    console.log(1)
    const arr = [5, 6, 7]

    for (const num of arr) {
      await this.$http.get("a-url");
      let i = 1;
     await new Promise((resolve, reject)=>{
       const interval = setInterval(async () => {
         i = i + 1
         await this.$http.get("b-url")
         num<6&&console.log(i)
         if(i === 4) {
           clearInterval(interval)
           resolve()
         }
       }, 1000)
      })
      console.log(num)
    }
    console.log(8)
  }

新问题就简单很多了,但是因为是使用 forEach() 所以就不能直接使用 async/await 了,得使用 Promise 来配合,但是代码会比较复杂,所以如果可以改成 for 循环的话,就可以使用 async/await 了,代码会精简很多:

async function fn(){
  console.log(1)
  const arr = [2, 3, 4]
  for(const num of arr){
    // 等待A请求完成
    await getAction('a-url')
    // 等待B请求完成,并输出 3 次 0
    await new Promise((resolve) => {
      let i = 0 // 题目中声明的位置错误会造成死循环,所以调整了位置
      const interval = setInterval(() => {
        getAction('b-url').then(() => {
          i = i + 1
          console.log(0)
          if(i === 3) {
            clearInterval(interval)
            resolve() // 完成 Promise
          }
        })
      }, 1000)
    })
    console.log(num)
  }
  console.log(5)
}
fn()

// 假请求函数
function getAction(url){
  return new Promise((resolve) => {
    setTimeout(() => { resolve() }, 300)
  })
}

正常业务场景里面是不会遇到这样的情况的,所以基本就是面试题,而且还是专门来为难面试者的了。

因为中间输出的 234 是重复输出,在浏览器控制台看似时输出了 2 3 4 但其实是合并后的,也就是输出的 2 2 2 3 3 3 4 4 4
所以不能简单使用 async/await 来同步执行,所以依旧使用 Promise 来实现。

魔改了代码,以便可以直接在浏览器控制台输出:

function fn(){
  console.log(1)
  const arr = [5, 6, 7]
  // 需要异步全部执行完毕之后输出 8 ,所以需要使用到 `Promise.all`
  Promise.all(arr.map(num => {
    return new Promise((resolve1) => {
      getAction('a-url').then(() => {
        // 中间输出的 2,3,4 是重复输出,也就是输出的 2,2,2,3,3,3,4,4,4
        // 所以不能简单使用 `async/await` 来同步执行,所以依旧使用 `Promise`
        const tmp = () => {
          return new Promise((resolve2) => {
            // 正常 A 请求完毕之后业务代码
            let i = 1;
            const interval = setInterval(() => {
              i = i + 1
              getAction('b-url').then(() => {
                console.log(i)
                if(i === 4) {
                  clearInterval(interval)
                  // 这里是个坑,直接 resolve 会有微任务插队的问题
                  setTimeout(() => resolve2(),0)
                }
              })
            }, 1000)
          })
        }
        // 2,2,2,3,3,3,4,4,4 输出完毕之后输出 num
        tmp().then(() => {
          console.log(num)
          resolve2()
        })
      })
    })
  })).then(() => {
    // 全部输出完毕后输出 8
    console.log(8)
  })
}
fn()

// 假请求函数
function getAction(url){
  return new Promise((resolve) => {
    // 异步事件不能过长,不然会超过 B 请求时 1000ms 的定时器,输出顺序会错位。
    setTimeout(() => { resolve() }, 200)
  })
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题