Day22:async-await

192 阅读8分钟

promise应用

promise其实是一个异步的操作, 相当于新开了一个线程去执行Promise中的代码

链式编程的注意点

第三行代码的执行情况只和第二行代码有关。只有第二行代码异常,第三行代码才会调用第二个回调函数

假设p2调用reject()方法,那么需要第二行第二个函数出现异常,第三行t2调用的then方法才会打印失败

假设p2调用resolve()方法,那么需要第二行第一个函数出现异常,第三行t2调用的then方法才会打印失败

同步和异步

同步

执行任务时,必须等待当前任务完成之后,才能执行下一个(排队执行

异步

执行任务时,不必等待当前任务完成之后,直接执行下一个

异步相当于重新开一个线程去执行其他任务

异步的概念

异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。

在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。

简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

以上是关于异步的概念的解释,接下来我们通俗地解释一下异步:异步就是从主线程发射一个子线程来完成任务。

promise其实是一个异步的操作

相当于新开了一个线程去执行Promise中的代码

链式编程的注意点

第三行代码的执行情况只和第二行代码有关。只有第二行代码出现了异常,第三行代码才会调用第二个回调函数

假设p2调用reject()方法,那么需要第二行第二个函数出现异常,第三行t2调用的then方法才会打印失败

假设p2调用resolve方法,那么需要第二行第一个函数出现异常,第三行才会打印失败

const p3 = new Promise((resovle,reject)=>{resovle('失败后的数据')})
const t3 = p3.then(()=>console.log(data),()=>console.log(abc)) 
// 这里resolve则data没有定义,报异常;reject则abc没有定义,报异常=>失败
t3.then(()=>console.log('成功'),()=>console.log('失败'))

平级调用

使用promise进行改进,利用then方法链式编程,实现了平级调用而不是嵌套调用,

/* 
1,分三次输出字符串,第一次间隔1秒后输出first,
第二次间隔2秒后输出second,第三次间隔3秒后输出third
    考虑使用promise实现
*/
new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("First");
    resolve();
  }, 1000);
}).then(() => {
  return new Promise((resolve, reject) => { 
    // 为了抽取方便,加上return。不写return new也可以,
    setTimeout(() => {
      console.log("Second");
      resolve();
    }, 2000);
  });
}).then(() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("Third");
      resolve();
    }, 3000);
  });
})

改进为封装函数调用的方法

封装方法中的return是为了返回一个对象,对象就可以继续调用then,如果不return,则会返回undefined,undefined调用then方法就会报错。

调用方法中的return是为了将promise内容抽成对象,对象就可以继续调用then,如果不return,则调用的执行时机难以控制

如果拆开写,则最后一个方法不用写return new,如果前面不写,则会无法控制第二个then的执行时机,不能实现可控连续调用,因此不推荐。

// 封装一个间隔打印的方法
function print(during,value){
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(value);
      resolve();
    }, during);
  });
}
print(1000,'first').then(()=>{
  return print(2000,'second')
}).then(()=>{
  return print(3000,'third')
})

1.async-await

async

概念

异步的意思

作用

可以将同步函数变成异步函数(调用async去修饰函数,返回的就是promise对象)

async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值

await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。

// 异步函数(函数返回的结果是promise对象
async function f2(){
  return 'aaa'
}
var res2 = f2()
console.log(res2) 
// Promise {<fulfilled>: 'aaa'}
// aaa存在于Promise的结果属性中
// 打印结果变成了fulfilled状态的promise
res2.then(data=>console.log(data)) // 这样才能拿到aaa

await

概念

等待

作用

可以等待他后面的promise对象,执行完之后,拿到结果属性中的数据(可以理解为promise返回的数据)

注意

await不能单独使用,必须和async连用,必须存在于异步函数中

await后面必须跟一个promise对象,如果不是promise对象,他会自动转成promise对象

await表达式返回的是promise执行后的结果(异步函数结果属性中的值)

间隔打印的await实现

// 封装一个间隔打印的方法
function print(during, value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(value);
      resolve();
    }, during);
  });
}
async function f() { // 必须写在异步函数里面
  await print(1000, 'first') // 直接拿到promise对象中的数据
  await print(2000, 'second')
  await print(3000, 'third')
}
f() // 间隔打印内容

2.事件循环

进程

内存中正在运行的一个程序,每一个应用至少有一个进程

线程

程序的一条执行路径

一个进程至少有一个线程,在进程开始之后,会自动创建一个线程来运行代码,这个线程就是主线程

多线程

一个程序有多条执行路径,每个路径可以执行不同的代码,完成不同的任务(360就是一个多线程程序)

浏览器

一个多进程、多线程的应用程序,它至少开启了三个进程

浏览器进程:页面展示,用户交互,子进程管理

网络进程:主要负责加载网络资源

渲染进程:开启一个渲染主程序,负责执行HTML,CSS和JS

为了避免相互影响,为了减少连环崩溃的几率,浏览器启动后会自动进行多进程

同步任务

在主线程上排队的任务,只有一个任务完成之后,才会通知执行下一个任务

异步任务

不进入主程序,而是进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

事件循环

Event Loop 是js或者node为解决单线程代码执行不阻塞主程序的一种机制(可以理解为异步

事件循环主要负责——执行代码,收集和处理事件,以及执行队列中的子任务

优先级问题

任务没有优先级,但是消息队列是有优先级的

由于浏览器复杂性急剧提升,W3C以及不再使用传统的宏队列和微队列方式

而是由浏览器来决定选取哪一个队列来执行

常见队列

微队列

存放需要最快执行的任务,优先级最高(vip通道

交互队列

存放用户操作产生的事件处理函数,优先级次之

延时队列

存放定时器里定义的回调函数,优先级最低

渲染主程序的执行流程

步骤:(只分析渲染主程序执行全局js代码这一块流程

  1. 主程序执行script标签下的代码
  2. 执行到给按钮添加事件代码时,会通知交互线程(异步线程),交互线程会将事件处理函数fn定义好,并监听按钮的点击。
  3. 一旦用户点击了按钮,交互线程就会把事件处理函数fn放到消息队列当中
  4. 主线程执行完同步代码之后,就会从消息队列当中拿到fn进行执行
    1. 首先执行的应该是给h1改变内容的赋值代码,再重新绘制页面
    2. 此时会产生一个新的任务——绘制任务(异步任务);线程会将这个任务添加到消息队列中
  1. 执行完赋值后,此时继续向下执行就会执行到delay。delay中相当于是一个循环三秒后fn才执行完的死循环。
  2. 主程序继续从消息队列中拿到绘制任务,执行。执行完之后,用户才能看到内容发生改变

如果在等待的三秒钟里,做了其他操作/点了其他按钮,都需要排队

<h1>xql</h1>
<button>111</button>
<script>
// 获取元素对象
const h1  = document.querySelector('h1')
const btn = document.querySelector('button')

// 演示同步阻塞情况
// 定义一个等待指定时间的方法,类似定时器,但不是定时器,定时器是异步
function delay(duration){
    // 先获取开始的时间
    const start = Date.now() // 开始的时间
    while(Date.now() - start < duration){} 
    // 每次循环判断时候的时间。对比开始的时候的时间和现在的时间,如果间隔小于指定时间,就等待
}
btn.addEventListener('click',function(){
    h1.innerHTML = 'hello world'
    delay(3000) // 这行代码阻塞了主渲染程序的执行
})

具体流程

  1. 渲染主程序开启,执行全局的js代码
  2. 执行setTimeout(function()的时候,计时线程启动,因为定时是0秒,所以计时线程会将回调函数fn,放到延时队列中,等待执行。
  3. 主线程接着往下执行,打印222,然后全局的js执行完成
  4. 主线程再去消息队列当中取出fn执行,再打印111
  5. 如果计时器等待时间小于阻塞时间,则阻塞结束计时器内容立刻执行
setTimeout(function(){
  console.log(111)
},0)

console.log(222)

具体流程

  1. 渲染主程序开启,执行全局js代码
  2. 渲染计时器,计时器线程启动,定时是0秒,计时器线程将回调函数fn添加到延时队列
  3. 主程序接着往下执行,执行promise,会将打印2的函数添加到微队列中
  4. 主程序接着往下执行,执行打印3,全局js代码执行完成
  5. 再去消息队列中取出微队列中的函数执行,打印2,然后取出fn,打印1

Promise.resolve()其实是new Promise((resolve,reject)=>{resolve()})的简写方式

promise的resolve()方法能改变代码的优先级,将其放到微队列中,在全局js代码执行结束后优先执行

setTimeout(function(){
  console.log(1)
},0)
// 使用promise的resolve()方法去改变代码的优先级
Promise.resolve().then(function(){
  console.log(2)
})
console.log(3)

promise继续.png