异步
首先,我们先用一段简单代码来了解一下异步的概念:
let a = 1 console.log(a, '第二行'); setTimeout(() => { a = 2 console.log(a, '第六行'); }, 1000) console.log(a, '第十行');
setTimeout
是一个内置函数,可以在它里面设置一个定时器,就是将该定时器里面的代码延迟执行,使用方式如上所示,1000表示延迟的时间一秒。
然后如上代码会怎么执行输出呢?
先输出第二行,然后先执行第十行,再返回来执行定时器里面的第十行,这种情况就展现了异步。
为什么呢?js中代码执行顺序是从上到下执行的,当执行完第二行时,执行到setTimeout
函数时,因为它里面有个定时器会将代码延迟执行,为了提高代码执行效率,js执行引擎会先将它放入一个队列中,然后执行下面的代码第十行,执行完不耗时的代码第十行,然后再执行耗时的第六行代码,所以输出结果如上所示。
综上:js是单线程语言,就是一次只能干一件事(多线程就是分俩条腿同时执行,执行效率快但是开销性能也多)。js遇到需要耗时执行的代码会将其先挂起,等到后续不耗时的代码执行完毕后再回过头来执行耗时的代码。
再举个例子;
function a() { setTimeout(() => { console.log('a 执行完毕'); }, 1000) } function b() { console.log('b 执行完毕'); } a() b()
先调用a函数,再调用b函数,这时候输出结果会是什么样的呢?
通过了解js是一门单线程语言,且js遇到需要耗时执行的代码会将其先挂起,等到后续不耗时的代码执行完毕后再回过头来执行耗时的代码,所以是会将a里面的耗时的代码先挂起,先执行完b中不耗时的代码再返回去执行a中耗时的代码。所以输出结果:先输出:b 执行完毕
过差不多一秒后输出a 执行完毕
。
解决异步
为什么要解决异步呢?
因为如果前面是一段发送http请求后端的数据,而发送http请求后端数据肯定要耗时,而下面代码要拿到后端返回出来的数据,如果不解决异步的话就直接执行下面代码,导致拿不到后端的数据,逻辑出错。
如下面代码;
function xq() { setTimeout(() => { console.log('章总相亲了'); }, 2000) } function marry() { console.log('章总结婚了'); } xq() marry()
输出:
原逻辑是要先相亲再结婚,因为相亲是耗时代码,执行时会先将它挂起,执行下面结婚,而这显然不符合原想法,那么要符合正常逻辑,于是就需要解决异步的问题。
解决异步的方法:
1. 回调
如图所示,可以将marry结婚函数放到定时器里面执行(称这个方法为回调),这样就可以解决异步的问题。打印结果:
但是如果代码过多,比如有几百上千个定时器,就需要在每一个定时器里面回调,这样就会导致代码嵌套过深,维护成本大,一旦出现问题很难排查,一般称这个为回调地狱。后面为了更加优雅,于是又打造了promise方法来解决异步问题。
2. promise
首先将上述逻辑使用promise来解决看下其语法;
function xq() { return new Promise((reslove, reject) => { setTimeout(() => { console.log('章总相亲了'); reslove() }, 2000) }) } function marry() { console.log('章总结婚了'); } xq().then(() => { marry() })
如示代码,在xq函数中return new Promise
,promise是官方提供的一个构造函数,接收一个函数体,函数体里面有俩个形参(建议写成reslove, reject),这俩个就相当于一个开关,resolve执行成功,reject执行失败,将相亲的过程放到promise里面去,执行完返回一个promise对象,然后通过xq().then()
调用,只有在promise接收的函数体内人为调用resolve成功状态then才能执行,then接收一个回调函数,当promise执行完成后再执行then里面的代码。最后答案输出:先相亲再结婚。
resolve里面还可以写入一个值,这个值可以被then后面的函数接收到,如下。
再看下面代码,假设结婚也需要时间;
function xq() { return new Promise((reslove, reject) => { setTimeout(() => { console.log('章总相亲了'); reslove('相亲顺利') }, 2000) }) } function marry() { setTimeout(() => { console.log('章总结婚了'); }, 1000) } function baby() { console.log('小章出生了'); } xq().then((res) => { console.log(res); //输出 相亲顺利 marry() baby() })
能不能输出正确结果呢?显然不能,baby是一个不耗时函数(当然baby还是要时间的,这里假设不要时间),而marry是一个耗时函数,会先执行不耗时的函数,所以结果如下;
那么应该怎么修改呢?所以需要在marry里面也添加promise,然后再marry().then(),调用。
xq().then((res) => { console.log(res); //输出 相亲顺利 marry().then(() => { baby() }) })
但是看这段代码,如果生一个足球队的话,那么就会marry.then(baby1.then(baby2.then(.....)),这里嵌套过多,代码不太优雅,所以官方对这个语法做了调整,如下。
xq() .then(() => { return marry() }) .then(() => { baby() })
.then
里面执行完直接return出marry(),因为marry()函数里面也是return出一个对象,然后后面就可以接着.then(baby),也就是then的链式调用。
前面知道了reslove是用来给promise提供一个成功状态的开关,且里面可以放值,可以被then后面的函数接收,如下,此时a()
就相当于Promise
通过new出来的一个实例对象,所以then方法是打造在Promise的原型上的,不管reslove调不调用都是可以.then
的,但是如果不调用reslove的话then后面的函数体就不会执行。
function a() { return new Promise(function (resolve, reject) { setTimeout(function () { console.log('a'); resolve('a 执行完毕') }, 1000) }) } a() .then(res => { console.log(res); })
那么reject是干嘛用的呢?
reslove是用来表示成功状态的且用.then接收,而reject则是用来表示失败状态的用.catch
接收(捕获异常)。里面可以加值也可以不加值。
reslove和reject就相当于一个开关,给出一个成功或失败的状态,但是调不调用这俩个,promise里面的代码都会正常执行,如果调用reslove成功状态.then后面的函数体就会执行,调用reject失败状态就执行.catch后面的函数体。
应用场景
接口请求拿数据,将其数据显示在页面上。如下图所示;
因为接口请求数据需要耗时,而下面的打印请求到的数据不耗时会立即执行,就会发生异步,导致数据还没拿到就打印,这样显然是打印不出来的。
于是可以使用promise解决异步,将请求过程封装在一个函数getDate()
,使得打印函数showList
接收到数据后再执行,在promise接收的函数中通过调用成功状态reslove
请求到的数据,用getDate().then()
后面的函数体的形参接收,再传给打印函数打印。