异步操作:async和await

async与await
  • async与await是关于Promise的语法糖,是比较新的异步操作的关键字,可以使得异步操作的代码接近常用的同步操作的代码
function fn(){
  return new Promise((resolve)=>{
    resolve(10)
  })
}
async function fn2(){
  return 10
}
let res1 = fn2()
let res2 = await fn2()
  • 可以用下面这个 async 声明的 fn2 代替 fn 函数,两者返回值完全一样,都是Promise对象
  • async 声明的函数内部可以用 await 调用其他异步函数
  • 我们可以在调用异步函数时在其前面加上 await 关键字,await会等待 fn2 执行完毕并获取返回值,res1 得到的是 Promise 对象,res2 在 await 的作用下直接获取 fn2 返回值 10
  • 但是这样使用 await 会导致阻塞代码,后面如果还有语句将不能立刻执行,反而使得异步没有意义,我们通常在 async 声明的函数中使用 await,这样会使得 await 仅仅阻塞该函数内部在 await 后面的部分,对函数外没有影响
  • 使用 await 之后,其对应的 catch 函数没法调用,所以我们之间使用 try-catch 结构达到同样效果,通过后面这个例子解释
常规实例
function main(){      //主函数
  fn2()
  doSomething()   //代表fn2执行期间也会执行的任何事
}
async function fn2(){
  try{
    let res = await fn3()
    //块1,成功完成fn3之后的代码
  }
  catch(err){
    //块2,fn3失败之后的代码
  }
}
main()
  • fn3 是个异步函数,只有 await 之后块2才会被 await 阻塞,fn2 外的 doSomething 就不会,这就实现了异步的价值。同时也保证了异步返回失败也能被发现
异步的深入理解

假设有一个异步函数 getData(),它会向服务器请求数据,然后返回结果。代码如下:

async function getData() {
  const response = await fetch('https://example.com/data');
  const data = await response.json();
  return data;
}
async function main() {
  console.log(1);
  const data = await getData();
  console.log(data);
  console.log(2);
}
main();

  在 main() 函数中,首先会执行 console.log(1),然后调用异步函数 getData()。由于 getData() 是异步函数,它会立即返回一个 Promise 对象,并且异步执行其中的操作。在异步操作完成之前,await 会暂停执行,并将控制权交回给调用者。因此,console.log(2) 不会立即执行,而是等待异步操作完成之后才会执行。
当异步操作完成并返回结果时,await 才会继续执行后面的代码。在这个例子中,异步操作是向服务器请求数据,等待服务器返回数据。当数据返回并解析完成之后,await 会继续执行后面的代码,即 console.log(data)console.log(2)
如果 console.log(data)console.log(2) 不需要使用异步操作的结果,它们可以在异步操作执行的同时执行,如下所示:

async function main() {
  console.log(1);
  const promise = getData();
  console.log(2);
  const data = await promise;
  console.log(data);
}

  在这个例子中,console.log(2) 在调用异步函数之后立即执行,不需要等待异步操作完成。当异步操作完成并返回结果时,await 才会继续执行后面的代码,即 console.log(data)
  在例子中,getData() 是一个异步函数,当执行 const promise = getData() 时,getData() 函数会被调用,但是不会立即执行异步操作,而是立即返回一个 Promise 对象,然后继续执行后面的代码。当执行 await promise 语句时,promise 对象的状态可能是 pending,也可能是 fulfilled 或 rejected。如果 promise 对象的状态是 pending,那么 await 会暂停后面的代码执行,等待 promise 对象的状态变为 fulfilled 或 rejected,然后返回 promise 对象的值或抛出 promise 对象的错误。如果 promise 对象的状态已经是 fulfilled 或 rejected,那么 await 会立即返回 promise 对象的值或抛出 promise 对象的错误。
因此,getData() 的异步操作可能会在 const promise = getData()await promise 之间的任何时间点开始执行,这取决于异步操作的实现方式和执行时间。当 await promise 暂停后面的代码执行时,异步操作可能已经完成,也可能还在进行中。如果异步操作已经完成,await 会立即返回异步操作的结果;如果异步操作还在进行中,await 会暂停后面的代码执行,等待异步操作完成。

  • 也就是当调用一个异步函数之后(调用的语句没有用 await),这行代码会直接结束返回一个结果(函数本身可能开始执行,也可能还没有),在函数外面此时代码也会继续往下走,函数外当遇到 await 所在的代码中有需要异步函数返回值时,函数外的代码就不会继续往下走了,直到异步函数执行完,await 及其之后的代码才会继续执行。await 就相当于信号灯,当异步函数出现过且遇到需要异步函数结果的语句时它变成红灯,异步函数外部代码暂停。
无意义的用法

  假设 getData() 是一个异步函数,有时候直接使用 await getData() 没有太大意义。因为异步函数被调用时会立即返回一个 Promise 对象,而 await 关键字会暂停后面的代码执行,等待 Promise 对象的状态变为 resolved 或 rejected,然后返回 Promise 对象的值或抛出 Promise 对象的错误。因此,如果在 await getData() 前面没有其他的同步代码需要执行,直接使用 getData() 也可以得到相同的结果。
例如,下面的代码中,两种写法都是等效的:

async function main() {
  const data = await getData();
  console.log(data);
}

async function main() {
  const promise = getData();
  const data = await promise;
  console.log(data);
}

在这个例子中,getData() 是一个异步函数,返回一个 Promise 对象。在第一个写法中,直接使用 await getData() 等待 Promise 对象的状态变为 resolved,然后返回 Promise 对象的值。在第二个写法中,先将 getData() 的返回值赋给一个变量 promise,然后再使用 await promise 等待 Promise 对象的状态变为 resolved,这样也可以得到相同的结果。

项目实例
  • 再以实际应用(微信小程序)举例,我们方便演示穿插 console.log 函数表示代码执行顺序:
onLoad: async function() {
  let that = this
  wx.cloud.init({});      //初始化云托管环境
  console.log(1)
  let res = this.Get();      //获取云端用户授权码数据,保证获取成功再执行后面代码
  console.log(3)
  console.log(await res)
  console.log(5)
},
Get(){     //获取用户数据
  console.log(2)
  return new Promise((resolve, reject) => {
    let that = this
    const res = wx.cloud.callContainer({  //从云托管获取数据
      config: {
        env: "xxx",
      },
      path: '/get', 
      method: 'GET',
      header: {
        'X-WX-SERVICE': 'xxx',
      },
      success:function(res) {
        console.log(4)
        resolve("success")     //异步成功结束,返回字符串作为返回值
      },
      fail: function(err) {
          reject("fail")    //异步失败并结束,返回字符串作为返回值
      }
    });
  });
},
  • 在上述例子中,Get是一个异步执行的函数,使用原生的 Promise 结构,因为它以 Promise 形式返回,并且带有 resolve 和 reject 函数。我们在里面进行云托管获取数据,是一个需要耗时的操作,最终控制台输出顺序是1、2、3、4、success、5。也就是先正常执行异步函数 Get 之前的代码,然后 Get 被调用之后立刻开始执行,同时外部的代码也会继续执行,所以外面的3紧接着就输出了,之后外面遇到 await 关键字用到 Get 的返回值 res 停止执行,由于异步函数内获取数据花费了一段时间,4会比3更迟输出。最后 Get 执行完,res 得以打印,5才能跟着打印出来。
Promise结果的使用

Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。可以通过 then() 方法或 catch() 方法来查询 Promise 对象的状态。
当 Promise 对象的状态变为 fulfilled 时,then() 方法会被调用,可以在 then() 方法中获取 Promise 对象的值。例如:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, world!');
  }, 1000);
});
promise.then((value) => {
  console.log(value); // 输出:Hello, world!
});

当 Promise 对象的状态变为 rejected 时,catch() 方法会被调用,可以在 catch() 方法中获取 Promise 对象的错误。例如:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('Something went wrong!'));
  }, 1000);
});
promise.catch((error) => {
  console.error(error); // 输出:Error: Something went wrong!
});

可以使用 finally() 方法来注册一个回调函数,该回调函数在 Promise 对象的状态变为 fulfilled 或 rejected 时都会被调用。例如:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, world!');
  }, 1000);
});
promise
  .then((value) => {
    console.log(value); // 输出:Hello, world!
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log('Promise completed!');
  });

注意,then() 方法、catch() 方法和 finally() 方法返回的都是 Promise 对象,可以通过链式调用来处理 Promise 对象的状态。

  • 在 Promise 对象的 then() 方法之前添加的代码会和异步函数并发执行,不会被阻塞。当 Promise 对象的状态变为 fulfilled 时,then() 方法会被调用,但是在 then() 方法之前添加的代码还会继续执行,不会停止或阻塞。例如:
console.log('Start');
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, world!');
  }, 1000);
});
console.log('Promise created');
promise.then((value) => {
  console.log(value); // 输出:Hello, world!
});
console.log('End');

在这个例子中,StartPromise created 会被立即输出,然后异步操作会开始执行,但是在异步操作执行期间,**End 也会被立即输出**。当异步操作完成后,then() 方法会被调用,输出异步操作的结果。因此,在 promise.then() 之前的代码和 promise.then() 之后的代码是并发执行的,不会相互阻塞。只有 await 才能阻止后面的代码执行

多个异步函数调用
async fn(){
  const a = await fetch("http://.../post/1");
  const b = await fetch("http://.../post/2");
}

如上我们调用两个异步函数(fetch是经典的异步函数),用 await 将使得他们并不能并发进行,这不是我们想要的,可以改成如下:

async fn(){
  const PromiseA = fetch("http://.../post/1");
  const PromiseB = fetch("http://.../post/2");
  const [a,b] = await Promise.all([promiseA, promiseB]);
}
  • 我们将两个 fetch 异步进行,两者的 Promise 对象合并在数组中,然后通过 await 同时进入等待,这将使效率翻倍
  • 更进一步我们如果需要循环进行异步操作,希望所有异步操作都能真正地并发执行,可以使用炫酷的 for await:
async fn(){
  const promises = [
    AsyncFun(),
    AsyncFun(),
    AsyncFun(),
  ];
  for await(let result of promises){
    // ...
  }
  console.log("done")
}
fn();
  • 注意js中不能再异步函数中使用 forEach,因为它会立即返回结果,应当用传统的 for 语句或者上述方法
异步与多线程

js是一个单线程语言,那么对于有多线程能力的语言,异步编程和多线程都是处理并发任务的常见方式。它们各自有优缺点,下面是一些常见的优缺点:

  • 异步编程的优点:
  1. 资源消耗低。异步任务通常不需要创建新的线程或进程,可以复用现有的线程或进程,从而减少资源的消耗。
  2. 响应速度快。异步任务通常可以在后台执行,不会阻塞主线程或用户界面,从而提高响应速度和用户体验。
  3. 代码简洁。异步编程通常使用回调函数、Promise、async/await等简洁的语法糖,可以让代码更易读、易写和易于维护。
  • 异步编程的缺点:
  1. 调试困难。异步代码难以调试,特别是在多级回调和嵌套的情况下,会让调试变得更加复杂和困难。
  2. 可读性差。异步代码通常需要处理回调地狱、Promise链等复杂的嵌套结构,从而降低了代码的可读性和可维护性。
  3. 错误处理复杂。异步代码容易出现错误和异常,需要处理回调函数或Promise的错误处理函数,从而增加了代码复杂性和错误率。
  • 多线程的优点:
  1. 并行处理。多线程可以同时处理多个任务,提高了并行处理的能力和效率。
  2. 资源隔离。每个线程都有独立的内存空间和资源,可以避免不同线程之间的竞争和冲突。
  3. 稳定性高。多线程可以提高系统的稳定性和可用性,即使其中一个线程出现问题,也不会影响整个系统的运行。
  • 多线程的缺点:
  1. 资源消耗高。多线程需要创建新的线程或进程,需要消耗更多的内存和CPU资源,从而增加了系统的负载和成本。
  2. 竞争问题。多线程容易出现资源竞争和死锁等问题,需要进行复杂的同步和协调操作,从而增加了代码的复杂性和错误率。
  3. 调试困难。多线程代码难以调试,特别是在多个线程之间交互和通信的情况下,会让调试变得更加复杂和困难。
    综上所述,异步编程和多线程都有各自的优缺点,需要根据具体的应用场景和需求来选择适合的方式。在某些情况下,异步编程可以比多线程更加高效和简洁,而在其他情况下,多线程可能更适合处理复杂和密集的计算任务。
  • Copyrights © 2023-2025 LegendLeo Chen
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信