Javascript中sync、async和await、Promise的理解与使用

本文参考:《js中的同步和异步》、《大白话讲解Promise(一)》、《async 函数的含义和用法》,感谢!

代码测试使用菜鸟教程:《菜鸟教程在线编辑器

一、理解JavaScript、sync和async

JavaScript是一门单线程的语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。

因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务。但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验。因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。

sync(同步)任务:主线程上排队执行的任务。只有前一个任务执行完毕,才能继续执行下一个任务。(按代码书写的先后顺序执行,上一行代码执行完成才会执行下一行代码)

async(异步)任务:不进入主线程,而进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。(跟代码执行时间的长短有关,所需时间短的先执行完)

JavaScript的异步机制包括以下几个步骤:

(1)所有同步任务都在主线程上执行,行成一个执行栈
(2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
(3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,哪些对应的异步任务,于是异步任务结束等待状态,进入执行栈,开始执行
(4)主线程不断的重复上面的第三步

setTimeout是一个延迟函数,可以用它来模拟ajax请求,它是一个异步任务,不会进入主线程,而是进入任务队列,任务队列是一个先进先出的数据结构,第一个setTimeout延迟20ms,第二个setTimeout延迟10ms,所以第二个setTimeout会先执行,第一个setTimeout后执行。fun1()和fun3()位于主线程上,会顺序执行,所以上面的执行顺序是fun1()、fun3()、fun4()、fun2(),依次输出1,3,4,2。

二、异步编程方法

(1)回调函数

一般将回调函数提出来单独定义,更便于理解,如下:

下面,将回调函数在主函数内部的位置放到console.log('这里是主函数');上面,则运行结果如下:

(2)Promise

先在控制台看一下Promise这个构造函数都有哪些方法:

1、先创建一个Promise,Promise的构造函数接收一个参数,这个参数是一个函数。这个参数函数接收两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数,按照标准来讲,resolve是将Promise的状态置为fulfilled(完成),reject是将Promise的状态置为rejected(拒绝)。

这里return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象,但是我们并没有定义回调函数resolve的内部逻辑,因此不会调用回调函数resolve,因为无从调用。

2、那么,我们怎么样使用回调函数resolve()和reject(),这就要用到Promise对象上的then、catch方法了。

仔细一看,这个逻辑和上面讲的回调函数一样嘛!是的,实际上就是一样的逻辑。而Promise的优势在于多层回调,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

3、Promise链式操作的用法

从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

下一个then接收的是上一个then返回的Promise,在then方法中,也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了,比如我们把上面的代码修改成这样:

4、reject的用法:reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。如果使用了reject回调函数,则必须添加then方法的第二个参数,或者添加catch方法,否则会报错

5、catch的用法:它和then的第二个参数一样,用来指定reject的回调。效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到如下图的结果。

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

catch一般写在重要或者容易出错的then后面,catch捕捉其上的then中报的错误,一旦其上面的某个then有错,就会进到catch中,但是就如同try/catch语句一样,并不会影响catch后面语句的运行。

6、all的用法:all是全部的意思,all方法的效果实际上是「谁跑的慢,以谁为准执行回调」。all()接收数组作为参数,其回调函数返回的结果也是数组,即console.log(results);返回的是下图红框中的数组。具体可以看看下图的示例,红框中标注的setTimeou函数的延迟时间会影响各函数的执行顺序,但不会影响最终输出的数组中的元素顺序,结果数组的元素顺序同输入数组的元素顺序是一致的。

7、race的用法:race本身是赛跑的意思,效果实际上是「谁跑的快,以谁为准执行回调」。race()接收数组作为参数,console.log(results);返回的是下图红框中的“随便什么数据1”。具体可以看看下图的示例,红框中标注的setTimeou函数的延迟时间会影响各函数的执行顺序,同时输入数组中元素的先后顺序也会影响各函数执行的先后顺序(延迟时间一致时,数组中元素在前者先执行),因为存在这两个先后顺序,而赛跑只有一个第一名,所以race()最终只会返回一个回调函数的执行结果。

(3)async、await用法

1、很多人认为async 函数是Promise的升级版,异步操作的终极解决方案。

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

await是在等待一个Promise的异步返回。用await声明的Promise异步返回,必须等到触发的异步操作完成,有返回值的时候,才会接着执行函数体内后面的语句。

2、我们可以将上面的示例改写一下,使用 then 方法添加回调函数,在回调函数中获取resolve()返回的数据。可以看到,then的执行总是最后的。

3、上面提到的Promise链式操作的用法,也可以使用async/await方法改写:

4、去掉async/await对比一下结果,可以发现有async/await时,整个demo内是同步(按书写的先后顺序,可以人为控制谁先执行谁后执行)进行的,去掉则变为异步(按所需时间长短,比如请求后台数据时我们无法人为控制请求时间的长短,如果第二个请求需要使用第一个请求返回的数据,我们必须使用async/await,否则无法有效获取到第一个请求返回的数据)。

5、await 后面的 Promise 对象,运行结果可能是 rejected,如何不捕捉的话,会报错,我们可以通过then的第二个参数或者Promise的catch方法捕捉错误。

6、使用demo.then方法调用resolve回调函数获取输出结果时,我们不将catch放在await后面的Promise上,因为catch捕捉到错误后会继续向下执行,又因为catch已经捕捉了错误,就没有错误传递下去了,所以在之后的then中就会调动resolve回调函数,返回undefined,而这是不正确的。

7、注意事项:

await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

forEach是不支持通过改写为async/await函数实现同步操作的,想要使用循环达到同步效果,请使用for循环。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 10
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Async/awaitPromiseJavaScript 用于处理异步任务的两种不同技术。Async/await 是基于 Promise 的,它是一种更简洁,更易读的方式来完成异步任务。Async/await 是一种“暂停”和“继续”的方式,可以帮助编写更加清晰和优雅的异步代码。Promise 是一种非阻塞的方式来完成异步任务,它提供了一种定义任务的标准,可以让异步任务更容易理解和管理。 ### 回答2: async/await是ES2017引入的用于处理异步操作的语法糖,它是建立在Promise的基础上的。 Promise是一种用于处理异步操作的对象,它有三种状态:pending(进行)、fulfilled(已成功)和rejected(已失败)。通过调用Promise的then()方法和catch()方法,我们可以分别处理成功和失败的情况。 而async/await是让我们以同步的方式编写异步代码的方法,它基于Promise,提供了一种更简洁、更易读的方式来处理异步操作的结果。 通过在函数前面添加async关键字,我们可以声明一个异步函数,这个函数会返回一个Promise对象。在异步函数内部,我们可以使用await关键字来暂停函数的执行,等待Promise对象的状态变为fulfilled或rejected。在await后面,我们可以写上一个Promise对象,然后使用一个变量来接收这个Promise对象fulfilled的值。 使用async/await可以使我们的代码更加清晰和简洁,而不需要在每个异步操作都写.then()和.catch()方法。我们可以通过try-catch结构来处理异步操作的错误,使得代码更容易理解和维护。 总而言之,async/await是基于Promise的一种用于处理异步操作的语法糖,它能够以更简洁、更易读的方式来处理异步操作的结果,使得我们的代码更加清晰和易于理解。 ### 回答3: async/awaitPromiseJavaScript处理异步操作的两种不同的机制。 Promise是ECMAScript 6引入的一种标准化的处理异步操作的方案。它是一个对象,表示一个尚未完成但最终会被解决的操作。通过.then()和.catch()方法,我们可以在Promise对象指定操作成功或失败后要执行的代码。 async/await是在ES8引入的语法糖,它基于Promise,使异步代码的书写更加简洁。通过在函数前面添加async关键字,可以将该函数定义为一个异步函数。在异步函数内部,我们可以使用await关键字来暂停执行,等待Promise对象的解决,并返回解决后的值。 async/await的优点在于可以使异步代码看起来像同步代码一样,更加清晰易读。它通过暂停函数的执行,避免了回调地狱和多层嵌套的问题。同时,它兼容Promise,可以使用Promise.all()、Promise.race()等方法处理多个异步操作。 使用async/await时,我们可以通过try-catch语句来捕获异步操作的错误。在异步函数内部可以使用多个await语句,用于按顺序执行多个异步操作。 需要注意的是,async/await只能在原生支持Promise的环境使用。但是,可以使用Babel等工具将其转换为ES5代码,以便在不支持Promise的旧浏览器使用。 综上所述,async/await是基于Promise的一种改进,并提供了更加简洁和可读性强的书写方式,用于处理JavaScript的异步操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值