异步

首先,我们先用一段简单代码来了解一下异步的概念:

let a = 1
console.log(a, '第二行');

setTimeout(() => {
  a = 2
  console.log(a, '第六行');
}, 1000)

console.log(a, '第十行');


setTimeout是一个内置函数,可以在它里面设置一个定时器,就是将该定时器里面的代码延迟执行,使用方式如上所示,1000表示延迟的时间一秒。

然后如上代码会怎么执行输出呢?

image.png

先输出第二行,然后先执行第十行,再返回来执行定时器里面的第十行,这种情况就展现了异步

为什么呢?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() 后面的函数体的形参接收,再传给打印函数打印。