《无所不能的JavaScript · 异步编程》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

CSDN.gif

写在前面的话

异步编程允许我们在执行一个长时间任务时,程序不需要等待,而是继续执行之后的代码,直到任务完成后再通知你,通常是以回调函数的形式。
这种编程模式,避免了程序的阻塞,提高了CPU的执行效率,用户体验得到了提升。
以 Java 中异步编程为例,其用法丰富多彩,可以利用JUC等各种工具方法实现多线程效果,以此提升系统系统,比如下面示例代码。
自定义一个 Callable 接口,利用线程池提交,返回 Future 对象,通过调用 get 方法阻塞等待结果。

Future<?> future = executorService .submit(new MyCallable());
System.out.println("结果为:" + future.get());

回到正题,接下来介绍一下 JavaScript 中异步编程的运用,让我们开始。


JavaScript 异步编程

技术简介

JavaScript从设计之初就是一个单线程的编程语言,浏览器无论在什么时候都有且只有一个线程在运行JavaScript程序,即同一时间只执行一条代码,所以每一个JavaScript代码执行块会“阻塞”其它异步事件的执行。
但依然不能阻止JavaScript实现异步编程效果,来一段示例代码:

setTimeout(() => {
  console.log('Hello World')
}, 1000)

console.log('123')

很明显,先输出123,再输出Hello World,这里可以看一下之前的这篇博文:《setTimeout 简笔》
JavaScript的单线程的异步编程方式其实有诸多优点,由于所有操作都运行在同一个线程中,无须考虑线程同步和资源竞争的问题,从源头上避免了线程之间的频繁切换,降低了线程自身的开销。


回调地狱

JS 中常见的异步场景,除了 setTimeout,还有 AJAX、Axios、Fetch 等远程数据获取,都是利用回调函数实现异步效果。
回调函数虽然方便,但它有一个明显的缺点。
如果我们需要依次执行多个异步操作,代码可能变成下面这样,典型的“向右编程”,可读性和可控性都很差。

setTimeout(() => {
  console.log('Hello World')
  setTimeout(() => {
    console.log('Hello World')
    setTimeout(() => {
      console.log('Hello World')
    },1000)   
  },1000)
},1000)

这种情况也被叫做函数的“回调地狱”。
为了解决这个问题,Promise应运而生。


Promise

Promise 是ES6引进的关键,它一个对象,代表了一个异步操作的最终完成(或失败)及其结果。它允许你为异步操作的结果提供一个回调函数,而不是使用传统的回调函数嵌套。

Promise 的创建:
使用 new Promise() 构造函数,并提供一个执行器函数(executor),这个函数接受两个参数:resolve 和 reject。resolve 用于解决(fulfill)Promise,而 reject 用于拒绝(reject)Promise。

Promise 的状态:
pending(等待态)、fulfilled(完成态)、rejected(拒绝态)。

Promise 的方法:
.then() 用于处理 Promise 成功的结果,.catch() 用于处理 Promise 失败的结果,.finally() 用于在 Promise 成功或失败后执行一些操作。

Promise 的示例:

// 使用new Promise方式改写,意义其实不大,这个本身其实就是
// 这段代码手动创建了一个Promise,并在其构造函数中提供了执行函数,这个函数接收两个参数:resolve和reject。这两个函数分别用于在异步操作成功或失败时调用。
// 当this.$axios.get('/api/hello')成功返回响应时,我们调用resolve并传递响应数据。如果发生错误,我们调用reject并传递错误对象。
// 然后,你可以链式调用.then()来处理成功的结果,以及.catch()来处理任何可能出现的错误。这种方式允许你以更传统的方式处理异步操作
// 但它通常没有使用async/await那么简洁和直观。
let sayHello = new Promise((resolve, reject) => {
  this.$axios.get('/api/hello')
      .then(response => {
        resolve(response);
      })
      .catch(error => {
        reject(error);
      });
})
sayHello.then(response => {
  console.log('使用new Promise方式改写,成功:' + response.data)
}).catch(error => {
  console.log('使用new Promise方式改写,失败:' + error)
});

Tips:上面都是一些理论,接下来用企业开发常用的 Fetch 技术加以说明。

常用的fetchAxios都是基于Promise的,这意味着它们都使用 Promise 来处理异步操作。
浏览器直接输入fetch函数,可以看到返回值是一个 Promise 对象,Promise 字面意思是承诺,承诺这个请求会在未来某个时刻返回数据。
image.png
那是怎么解决回调地域的?可以看下面示例代码,Promise 的链式调用避免了代码的层层嵌套,很明显不再是“向右编程”,而是“向下编程”。

fetch('http://127.0.0.1:8082/api/hello')
  .then(response => {return response.json()})
  .then(data => { console.log("get.data:", data)})
  .catch(error => {console.log("get.error:", error.message)})
  .finally(() => {console.log("get.finally")})

//输出:get.data: Hello, Spring Boot 2!
//输出:get.finally

如果在链式调用的时候,遇到错误,可以用catch,如果之前任意一个阶段报错,都会被catch捕捉,后续流程则不执行。
同时可以添加finally做兜底动作,无论失败与否都会执行,这里面可以执行 Loading 动画关闭等事情。
其实和 Java 的try-catch-finally代码块差不多,链式调用就有点像 Java8 的 Stream。
好吧,再次证明语言是互通的,条条大路通罗马。


async、await

async 和 await 是 ES2017(ES8)中引入的,它们基于 Promise 提供了更简洁的语法来编写异步代码。
简单来说,它们是基于 Promise 之上的一个语法糖,可以让异步操作更加简单明了。

【基础使用】
如下代码,函数添加了 async 代表是异步函数,内部利用 await 可以阻塞该操作,等待结果的返回。
这里的 this.$axios.get返回的是一个 Promise 对象,await 通常搭配其使用,当然也可以跟同步代码,都会阻塞。
注意,此时如果函数外有其他逻辑,不会被阻塞。

async fetchData() {
  try {
    const response = await this.$axios.get('/api/sleep');
    this.data = response.data;
  } catch (error) {
    this.error = error.message;
  }
},

【补充说明】
并不是 async 函数就必须和 await 一起使用。你可以声明一个 async 函数,但完全不使用 await 关键字。在这种情况下,函数体会同步执行,并且函数会直接返回一个状态为 fulfilled 的 Promise,其值为函数的返回值。
但是,await 关键字只能在 async 函数内部使用。这意味着你不能在非 async 函数中使用 await。

【再一个示例】


// 定义一个返回 Promise 的异步函数
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("data");
    }, 1000);
  });
}

// 使用 async 和 await 的函数
async function fetchDataAsync() {
  const data = await fetchData(); // 等待 fetchData() 解决并获取结果
  console.log(data); // 'data'
}

// 调用异步函数
fetchDataAsync();

在这段代码中,fetchData 返回一个 Promise,而 fetchDataAsync 是一个异步函数,它等待 fetchData 的 Promise 解决,然后打印结果。
使用 async 和 await 可以使异步代码看起来更像是同步代码,从而简化了异步编程的复杂性。


其他用法

【Promise.all】
如果 async 函数中,有多个异步请求逻辑,使用多个 await 关键词会导致效率较低。
此时可以通过 Promise.all 的方式改造,具体参考下面代码,效率提升接近一倍。

async fetchData() {
  
  // 创建两个Promise对象
  let handleOne = this.$axios.get('/api/hello');
  let handleTwo = this.$axios.get('/api/sleep');
  
  // Promise.all方法改写
  // 等数组中的所有promise对象都完成执行
  const [a, b] = await Promise.all([handleOne, handleTwo]);
  console.log(a)
  console.log(b)
}

还有一个更酷炫的写法,for await,也会等所有操作都执行完才向后执行。

async fetchData() {
  
  let handleOne = this.$axios.get('/api/hello');
  let handleTwo = this.$axios.get('/api/sleep');
  
  let asyncArr = [handleOne, handleTwo]
  for await (let result of asyncArr) {
    console.log(result)
  }

  console.log('done')
}

【Promise几种常用方法】
1、Promise.all()
将多个Promise封装成一个新的Promise,成功时返回的是一个结果数组,失败时,返回的是最先rejected状态的值。
使用场景:一次发送多个请求并根据请求顺序获取和使用数据。
2、Promise.race()
返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
简单来说,就是多个Promise中,哪个状态先变为成功或者失败,就返回哪个Promise的值。
3、Promise.any()
接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
4、Promise.allSettled()
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

【关于 async 和 await 的使用】
并不是 async 函数就必须和 await 一起使用。你可以声明一个 async 函数,但完全不使用 await 关键字。在这种情况下,函数体会同步执行,并且函数会直接返回一个状态为 fulfilled 的 Promise,其值为函数的返回值。
但是,await 关键字只能在 async 函数内部使用。这意味着你不能在非 async 函数中使用 await。

// 不使用 await 的 async 函数
async function synchronousFunction() {
  // 这里没有使用 await
  return "This is synchronous";
}

synchronousFunction().then(console.log); // "This is synchronous"

// 使用 await 的 async 函数
// await 使得 asynchronousFunction 在等待 Promise 解决之前不会继续执行。这提供了异步操作的一种更加直观和易于理解的方式。
async function asynchronousFunction() {
  const result = await new Promise(resolve => {
    setTimeout(() => {
      resolve("This is asynchronous");
    }, 1000);
  });
  return result;
}

asynchronousFunction().then(console.log); // "This is asynchronous"(在1秒后打印)

总结陈词

JavaScript 中的 Promise、await 和 async 是处理异步操作的关键概念和关键字,它们在处理异步编程时起着至关重要的作用。
让我们继续见证了 JavaScript 的博大精深,后续会更新更多内容。
💗 如果觉得内容还可以,麻烦点个关注不迷路,您的鼓励是我创作的动力。

CSDN_END.gif


参考链接:
阮一峰 - ES6
使用ES6的Promise完美解决回调地狱
Javascript异步编程的4种方法
Promise和async/await

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战神刘玉栋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值