Javascript 中的异步操作

JavaScript/前端
53
0
0
2024-03-17

最近看 JS 代码,对于 Promise 相关写法不是很熟悉,因此梳理了一下相关概念

Javascript 中的函数写法

在异步操作中会用到的回调函数通常使用匿名函数的写法,这里先复习一下 Javascript 中各种函数写法

function hello () {
    return "Hello world!"
}

// with parameters
function hello (name) {
    return "Hello " + name + "!"
}

// 匿名函数
function (name) {
    return "Hello " + name + "!"
}

// Arrow function
hello = () => { // 等价于 hello = function() {
  return "Hello World!";
}

// 省略 `{}` 和 `return`
hello = (name) => "Hello " + name + "!";
hello = (firstName, lastName) => "Hello " + firstName + " " + lastName + "!";
// 只有一个参数时,括号也可以省略
hello = name => "Hello " + name + "!";

P.S.:

  • 函数只能有一个返回值,如果需要返回多个值,可以把它们放到对象或数组中返回

Promise

Promise 的定义如下:A Promise is an object that represents the result of an asynchronous computation

Javascript 中异步执行的过程通过以下方式实现:

  • 函数调用会被放入 Call Stack
  • Promise callback function 会被放入 Microtask Queue
  • setTimeout, setInterval 等异步 web APIs 会被放入 Task Queue
  • Event Loop 会一直检查 call stack,当其为空时会将 microtask queue 中的 callback function 放入 call stack,当 call stack 和 microtask queue 均为空时才会处理 task queue

创建 Promise 对象

let myPromise = new Promise((resolve, reject) => {
  // do something may not get result immediately
  const v = Math.random()
  console.log(v)
  if ( v > 0.5 ) { // some condition
    // case on some condition
    // call resolve callback function and pass result data as argument
    resolve({ data: 'Here is your data!' })
  } else {
    // case on other condition
    // call reject callback function and pass error as argument
    reject(new Error('Network error'))
  }
})
console.log("promise defined")

上述代码在 Promise 对象创建时会立即允许里面代码,在调用 resolve(res)/reject(err) 时会改变 Promise 对象的状态,这时 Promise 会进入成功/失败状态

调用 Promise.thenPromise.catch 会将里面的 callback 函数放入 microtask queue,等待 Promise 进入成功/失败状态后且 同步代码运行完后调用 callback 函数

// code in the Promise block get executed when created
// code in the `then/catch` block wait until the Promise enter resolved/rejected state
myPromise
  .then(result => {
    console.log('Data:', result.data)
  })
  .catch(error => {
  	console.error('Error:', error.message)  
  })

定义异步函数

function fetchData() {
  return new Promise((resolve, reject) => {
    console.log(Date.now() + ": promise start")
    const v = Math.random()
    if (v > 0.5) {
      resolve({ data: "hello" })
    } else {
      reject(new Error('Network error'))
    }
    console.log(Date.now() + ": promise end")
  })
}

这时 Promise 内部的代码并不是立即执行,而是在调用 fetchData 函数时执行,下面的代码会立即执行 Promise 的内容,并等待 Promise 状态改变后执行传入 then/catch 的回调函数

fetchData()
  .then((res) => {
    console.log(Date.now() + ": promise resolved")
    console.log(res.data)
  })
  .catch((err) => {
    console.log(Date.now() + ": promise rejected")
  })

链式调用:传入 .then(callback1) 的回调函数可以返回值,这个值会作为参数被传到下一个 .then(callback2) 的回调函数

let promise = new Promise((resolve, reject) => {
    resolve(1)
  })
  
promise
.then(res => {
    console.log(res) // 输出 1
    return res + 1
})
.then(res => {
    console.log(res) // 输出 2
    return res + 1
})
.then(res => {
    console.log(res) // 输出 3
    return res + 1
})

如果回调函数返回了一个 Promise 对象,那么下一个 .then(callback) 同样会等待上一个回调函数的执行

promise
  .then(value => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(value + 1);
      }, 1000);
    });
  })
  .then(value => {
    console.log(value); // 输出 2,但是在 1 秒后
  });

await/async

是 ES7 中引入的新特性,具体用法如下

async function 关键字定义的函数,自动将返回值包装成一个 Promise,如果正常返回就是 resolved 状态,如果有异常则为 rejected 状态

async function asyncSleep(time) {
  setTimeout(() => {
    console.log(Date.now() + ": asyncSleep")
  }, time * 1000)
}
// 等价于
function sleep(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(Date.now() + ": sleep")
    }, time * 1000)
    resolve()
  })
}

await 只能用在用 async 定义的函数内部, 用于暂停执行等待某个 async 函数的返回

function sleep(time) {
  return new Promise((resolve, reject) => { setTimeout(() => {
      console.log(Date.now() + ": sleep")
    }, time * 1000)
    resolve()
  })
}

async function asyncSleep(time) {
  console.log(Date.now() + ": asyncSleep start")
  await sleep(time)
  console.log(Date.now() + ": asyncSleep end")
}

asyncSleep(1)
  .then(() => {
    console.log(Date.now() + ": asyncSleep resolved")
  })