JS 异步

回调函数

ES5:使用回调函数处理异步执行的结果

setTimeout(() => {
  console.log('1')
  setTimeout(() => {
    console.log('2')
    setTimeout(() => {
      console.log('3')
      //....
    }, 1000)
  }, 1000)
}, 1000)

多重回调嵌套导致可读性变差,称为回调地狱(Callback Hell)

良好的编程习惯:http://callbackhell.com/

Promise

ES6语法

承诺,承诺在未来的某个时刻返回数据。

优点:使用链式结构将多个异步操作串联起来,解决了回调地狱问题

new Promise(...)
.then(...)
.then(...)
.then(...)
.catch(...)
.finally(...)

Promise.then() 和 Promise.catch() 都返回 Promise 对象,示例:

new Promise(function (resolve, reject) {
  setTimeout(() => {
    var start = 3
    resolve(start)
  }, 1000)
})
  // 异常之后、catch之前的部分不被执行。类似 try-catch中的异常捕获
  .then(function (res) {
    console.log('1st then, ', res)
    throw Error('e')
    res *= 2
    return res
  })
  .then(function (res) {
    console.log('2nd then, ', res)
    return res * 2
  })
  .then((res) => {
    console.log('3rd then, ', res)
  })
	// 异常捕获
  .catch((err) => {
    console.log('err, ', err)
  })
  // 异常之后的then可执行
  .then(() => {
    console.log('after catch')
  })
  // 清理、结尾工作,比如关闭进度条
  .finally(() => {
    console.log('final')
  })

基于Promise的异步请求

<script>
  'use strict'
  function reqPromise(URL) {
    return new Promise((resolve, reject) => {
      var xhr = new XMLHttpRequest()
      xhr.open('GET', URL)
      xhr.onload = function () {
        if (xhr.status == 200) {
          resolve(xhr.response)
        } else {
          reject(Error('error code: ', xhr.statusText))
        }
      }
      xhr.onerror = function () {
        reject(Error('network error.'))
      }
      xhr.send()
    })
  }
  reqPromise('https://jsonplaceholder.typicode.com/posts/1').then(
    (resp) => {
      console.log('yay!, ', JSON.parse(resp))
    },
    (Error) => {
      console.log(Error)
    }
  )
</script>

基于fetch-api(XMLHttpRequest的升级版) 的异步请求

fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'get',
})	// fetch 返回 Promise对象
  .then((resp) => {
    return resp.json()	// Body.json() 返回 Promise 对象
  })
  .then((data) => {
    console.log(data)
  })

参考:MDN · Promise

async/await

基于 Promise 之上的语法糖,使异步操作简单、明了。

注意:

  • 使用 async 将函数标记为异步函数(返回 Promise 对象的函数)

  • 在异步函数中调用其他的异步函数,不再使用 then,而是使用await,await 会等待 Promise 执行完成返回最终的结果(await 底层基于 Promise 和事件循环机制实现,在等待时会处理其他任务,比如页面更新等等)

async function fun() {
  const resp = await fetch('https://jsonplaceholder.typicode.com/posts/1')
  const data = await resp.json()
  console.log(data)
}

fun();

await 陷阱 ✧

1、并行操作

async function f() {
  const promiseA = await fetch('http://...')
  const promiseB = await fetch('http://...')	//两个 fetch 不是并行的
  
  //...
}

// 更高效的做法:将Promse用Promise.all组合起来,然后再去await,修改后的程序运行效率会直接提升一倍
async function f() {
  const promiseA = fetch('http://...')
  const promiseB = fetch('http://...')
  
  const [a, b] = await Promise.all([promiseA, promiseB])
}


不使用Promise.all时:

使用Promise.all后:

2、再循环中执行异步操作,不能调用 forEach或map 这样的方法

async function f1() {
  [1, 3, 5].forEach(async (i) => {	//⚠️:虽然使用了await,但forEach会立刻返回,并不会暂停等到所有异步操作都执行完毕!
    await doSomethingOperation();
  })
  consolo.log('done')
}
f1();


//解决方法一:使用传统的for循环 =》 会暂停等到所有异步操作都执行完毕
async function f2() {
  for (let i of [1, 2, 3]) {
    await doSomething();
  }
  consolo.log('done')
}

//解决方法二:使循环中的操作并发执行,使用 for await:
async function f3() {
  const promises = [
    someAsyncOp();
    someAsyncOp();
    someAsyncOp();  
  ]
  for await (let result of promises) {
    // ....
  }
  console.log('done')
}

3、不能在全局中或普通函数中使用 await,而只能被用在异步函数中

async funcion f() {
  await doSomething();
}
f();

// 更简洁
(async ()=> {
  doSomething()
})();

有了 async/await,几乎用不到 Promise 及他的 then、catch 方法

其他:引入并使用 async 模块

Reference

https://www.bilibili.com/video/BV1WP4y187Tu

posted @ 2023-02-19 22:49  egu0o  阅读(41)  评论(0编辑  收藏  举报