当前位置: 代码迷 >> 综合 >> JS中的异步编程(Promise和async/await)
  详细解决方案

JS中的异步编程(Promise和async/await)

热度:101   发布时间:2023-10-28 05:45:09.0

文章目录

      • 前言
      • callback
      • JS异步编程原理与回调函数
      • Promise
      • await和async
      • 例子
      • 总结

前言

JS执行是单线程的,但是在JS中需要有大量进行查询、获取数据的操作,例如AJAX,如果都按照顺序执行,那么在用户体验等多个方面肯定是极差的。那么就衍生了一系列的异步操作。

callback

最简单,最早产生的异步解决方案就是callback,常说的回调函数,举个例子:

 var A=function(){
    // var aa= fs.readFileSync('test.txt',{encoding:'utf-8'})// console.log(aa)for(var i=0;i<999;i++){
    }console.log('A')}var B=function(callback){
    callback()console.log('B')}B(A)

执行后结果如下:
A
B
这个感觉不对劲,不是说callback是异步吗,为什么还是顺序执行呢。(需要注意的是callback只是一种将异步编程同步化的解决方案而不是本身是异步的)其实callback函数仍然在主线程中执行,所以,调用callback,当前线程还是会去执行callback。网上有的解释并没有解释清楚。那callback异步操作到底有什么关系呢?看下边个例子:

console.log('A')
$('button').on('click',function(){
    console.log('success')
})
console.log('B')

执行结果如下:
A
B
为什么success没有被打印出来呢,应该学过JS都知道把,因为它的打印是有条件的,需要点击的时候才能触发,总之就是可以通过这个条件来控制回调函数的执行顺序,现在可能有人还是会不太理解。

JS异步编程原理与回调函数

ES6之前,JavaScript中异步编程分为3类:DOM事件(如onclick)、网络请求(如ajax)、定时器(setTimeout/setInterval)。他们均使用回调函数来进行异步调用。

JS执行栈是单线程,但是JS的宿主环境不是单线程的(如浏览器、Node),浏览器内部是允许多个线程同时运行的,除JavaScript引擎线程外,还有事件触发线程、HTTP请求线程、定时器触发线程,他们和JavaScript线程是互不影响的,不会造成阻塞,JS执行的线程(即JavaScript引擎的所在线程)为主线程,浏览器会执行一个类似于while的轮询过程,每次循环查看线程中是否有待处理的任务,(如浏览器点击事件、AJAX请求、定时器等),如果存在则把该任务添加到JS主线程的执行任务列表中等待执行。

那么我们就很好明白了,诸如事件请求、定时器、事件点击等确实是异步的,但是不是由于JavaScript本身的作用,JavaScript本身依旧是单线程的,当碰到请求等事件时,浏览器会新开一个线程,当回调函数callback调用的时候,那么之前的异步事件就产生了变更把回调函数放到JavaScript引擎任务队列中等待执行。

尽管回调函数是一种异步解决方案,但正是异步操作的问题,会产生回调地狱的问题,如下

function async(){
    setTimeout(function(){
      //回调函数1console.log(1);  setTimeout(function(){
      //回调函数2console.log(2);setTimeout(function(){
      //回调函数2console.log(3);},1000);},1000);},1000)}async();//调用结果:1s后打印1 2s后打印2 3s后打印3 

这是网上随便找的一个例子,我们可以看到一大堆的 {}(),看完脑袋有点疼,一旦嵌套层级变多,代码结构就变得很不乐观。

Promise

在这里插入图片描述
promiseES6中提出的一种异步解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。其只有then这一个方法,then方法带有两个参数:

  • 成功回调
  • 失败回调

promise对象有三个状态:

  • pending(进行中,未完成)
  • resolved(已完成)
  • rejected(已失败)

(1)我们将上边的callback回调函数包装成Promise

function async1(data){
    console.log(data)var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    resolve(2)},1000)})return p
}
function async2(data){
    console.log(data)var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    resolve(3)},1000)})return p
}  

(2)使用then链式调用这三个方法

async()
.then(function(data){
    return async1(data)
})
.then(function(data){
    return async2(data)
})
.then(function(data){
    console.log(data)
})  

(3)还可以简化为

async()
.then((data)=> async1(data))
.then((data)=> async2(data))
.then((data)=>console.log(data))
//调用结果:1s后打印1 2s后打印2 3s后打印3 

我们可以看到异步操作的结构清晰了许多,当然我们可以通过rejected对异步操作进行错误,我们对函数进行错误处理。

  function async(){
    var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    resolve(1)},1000)})return p
}function async1(data){
    console.log(data)var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    reject(2)},1000)})return p
}
function async2(data){
    console.log(data)var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    resolve(3)},1000)})return p
}

执行结果

1
(node:17952) UnhandledPromiseRejectionWarning: 2
(node:17952) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:17952) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

报错了,大概意思就是没对错误进行处理,使用catch方法捕捉错误。

function async(){
    var p=new Promise(function(resolve,reject){
    setTimeout(function(){
    reject('这是错误1')},1000)})return p
}function async1(data){
    console.log('这是正确回调')
}async()
.then(async1,(e)=>{
    console.log(e)})
//打印结果 这是错误1

我们还可以用catch方法来处理rejected回调

async()
.then(async1)
.catch((data)=>console.log(data)) 

catch方法的另一个作用是如果执行resolve时代码出错(抛出错误),不会卡死出错,会进到catch函数里边

await和async

在这里插入图片描述
async异步操作返回一个Promise对象可以使用then链式调用

async  function aa(){
    return 123}console.log(aa())//Promise {<resolved>: 123}console.log(aa().then(res=>console.log(res)))//Promise {<pending>}//123

await字面意思就是等待,在JavaScript中意思和这个差不多,async是异步操作,而await就是等待一个异步任务完成的结果。简单的说await是一个操作符,async是异步的方法。

await只能用在async

async  function demo(){
    const data1= await async()const data2=await async1()console.log(data1,data2)
}demo()
//打印结果 1 2

可以看到结果1和结果2同时打印,那么await操作符的作用就比较明显了,await就是将异步Promise同步化的一种解决方案。不需要在then链中进行回调操作。

例子

下边例子是使用Promis 和 原生 ajax 包装一个类似与fetch 的方法

var fetchOwn=function(url){
    var data=new Promise((resolve,reject)=>{
    var xml=new XMLHttpRequest()xml.open('GET',url,true)xml.send()xml.onreadystatechange=function(){
    if(xml.status==200&&xml.readyState==4){
    resolve(xml.response)}}})return data
}
//解析json 为js对象
var fetchJson=function(json){
    var data=new Promise((resolve,reject)=>{
    resolve(JSON.parse(json))})return data
}fetchOwn('http://jsonplaceholder.typicode.com/comments')
.then(e=>fetchJson(e))
.then(res=>console.log(res))

总结

  1. JavaScript本身仍然是单线程的,没有异步操作一说,但是其宿主环境是多线程的问题,造成可以进行一些异步操作。
  2. callback本身也不是异步操作的但是结合一些浏览器事件HTTP请求等造成了callback回调异步操作的问题。
  3. callback回调函数会造成地狱回调的问题,代码结构不清晰,Promise对象很好的解决课这个问题。
  4. await是操作符,是async同步化的一种方法。
  相关解决方案