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

文章目录

      • 前言
      • 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(){  //回调函数1
        console.log(1);  
       setTimeout(function(){  //回调函数2
          console.log(2);
          setTimeout(function(){  //回调函数2
            console.log(3);
         },1000);
      },1000);
     },1000)
   }
   async();

   //调用结果:1s后打印1 2s后打印2 3s后打印3  

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

Promise

JS中的异步编程(Promise和async/await)_第1张图片
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

JS中的异步编程(Promise和async/await)_第2张图片
async异步操作返回一个Promise对象可以使用then链式调用

async  function aa(){
   return 123
 }
 console.log(aa())//Promise {: 123}
 console.log(aa().then(res=>console.log(res)))
 //Promise {}
 //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同步化的一种方法。

你可能感兴趣的:(前端文章)