likes
comments
collection
share

ES6中的异步编程

作者站长头像
站长
· 阅读数 11

我们知道JavaScript语言的执行环境是单线程,也就是一次只能完成一个任务。如果有多个任务就必须排队,前面一个任务完成,再执行后面的一个任务

这种模式虽然实现起来简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的有的浏览器无响应(假死,往往就是因为某一段JS代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种

  • 同步(Synchronous)
  • 异步(Asynchronous)

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;

"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

学习技巧 :精通回调函数 精通异步编程的思想

1. 回调函数

回调函数是异步操作的最基本的方法 当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数

示例:

function foo (callback) {
    setTimeout(() => {
        callback()
    }, 3000);
}

foo(function() {
    console.log('这是回调函数');
})

回调函数嵌套层级过多将产生回调地狱,不利于代码的编写和阅读。

  • 回调地狱的根本问题就是:

嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身 嵌套函数一多,就很难处理错误 当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。在接下来的几小节中,我们将来学习通过别的技术解决这些问题。

定时器 setTimeout比较常用,很多人认为setTimeout是延时多久,那就应该是多久后就执行 其实这个观点是错误的,因为JS是单线程执行的,如果前面的代码影响了性能,就会导致setTimeout不会按时执行

Promise/A+ Promist本意是承诺的意思,在程序中的意思就是承诺我过一段时间后会给你一个结果,什么时候会用到异步事件?答案是异步操作,异步操作指可能比较长时间才有结果的才做,比如网络请求,读取本地文件.

Pormise的三种状态

等待中(pending) 完成了(resolved) 拒绝了(rejected) 这个承诺一旦从等待状态变成了其他状态就永远不能更改状态,也就是说一旦状态变成了resolved后,就不能再次改变

2. Promise

  • 为了解决回调地狱,引入Promise。

  • Promise有三种状态Pending、Fulfilled、Rejected,其中Fulfilled和Rejected是结果,一旦状态改变到达结果后就不会再变了。到达Fulfilled和Rejected后都会有相应的回调作为响应,分别是onFulfilled和onRejected。

  • 简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。

语法

1.promise是一个es5

2.promise是一个构造函数 创建了一个数据容器

3.map set arr object 被动产生数据

4.promise主动产生数据

promise 的基本示例

const promise = new Promise((resolve, reject) => {
    resolve('成功的回调')
    reject(new Error('失败的回调'))  // 这个不会执行,状态一旦改变就不会执行了
})

promise.then(value => {
    console.log(value)
}, error => {
    console.log(error);
})

Promise链式调用

要时用链式调用来代替promise的嵌套调用

  • promise通过.then后返回一个全新的promise对象,新的promise对象的状态取决于上一个promise的状态

  • 如果不断地调用then方法,这里的每一个then方法都是为上一个then方法返回的promise对象提供一个状态明确后的回调

  • 我们也可以在then方法中手动返回一个promise对象,这样一来下面的then的中接收到的就是这个promise对象的状态改变后的回调了,then中的回调函数的参数就是这个promise最终的执行结果,所以这样就可以通过链式调用then,并且then中不断return新的promise来实现多个异步任务链式调用执行来避免嵌套了。

  • 前面的then方法中回调函数的返回值会作为后面then方法回调的参数。

  • 如果上一个then中return的不是一个promise,那么下一个then中的回调参数中接收到的就是这个非promise值。

  • 如果上一个没有返回任何值,那么下一个then中的回调函数的参数接收的就是undefined

new Promise((resolve, reject) => {
  resolve('success')
  // 无效
  reject('reject')
})


当我们在构造Promist的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh

Promise的链式调用
每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)
如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调
如果then中出现异常,会走下一个then的失败回调
如果then中使用了return,那么return的值会被Promise.resolve()包装
Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

then中可以不传递参数,如果不传递参数会透到下一个then中
catch会捕获到没有捕获的异常
Promise也很好的解决了回调地狱的问题

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

优点:回调函数变成了链式写法,每个异步函数执行完成后,才会去执行下一个then函数; 缺点:Promise的写法只是回调函数的改进,用then()方法免去了嵌套,更为直观。但这样写也存在了很明显的问题,代码变得冗杂了,语义化并不强

promise 异常处理

  • 在promise中抛出异常(比如调用了不存在的函数啥的或者手动抛出一个异常),这种情况下回触发then的第二函数参数回调,也就是onRejected回调来捕获异常。当然我们更常用catch方法来捕获异常。更符合链式调用的风格。

  • 但是两种错误的捕获有不同的地方,首先then的链式捕获只能捕获当前处理的promise对象中的错误。而.catch会捕获链条从一开始传递下来的异常。

let p_success = new Promise((resolve,reject) =>{
    resolve('成功')
})

let p_fail = new Promise((resolve,reject) =>{
    reject('失败')
})

// 用then来捕获失败
p_success
.then(function onFulfilled (value) {
    console.log('onFulfilled', value)   // onFulfilled 成功
    return p_fail
}, function onRejected (error) {
    console.log('onRejected', error)	// 捕获不到P_fail
})



// 用catch来捕获失败
p_success
.then(function onFulfilled (value) {
    console.log('onFulfilled', value)	// onFulfilled 成功
    return p_fail
})
.catch(function onRejected (error) {
    console.log('onRejected', error)	// onRejected 失败
})

then函数的返回值是一个新的promise对象,是then传入的回调函数的返回值:

1、如果是一个promise对象,就是他

2、如果不是,那就把函数的结果包装为一个生成了数据的promise对象

p1.then((data)=>{console.log(data,xxx)})

3、promise函数不是异步非阻塞函数,但创建的对象引用的then函数是

异步编程中的最难的 任务的队列分类 事件循环

任务就指的是js代码中运行的代码,分为两种 同步任务 异步任务

注意:异步任务的队列优先级 : 异步宏任务先执行,然后执行异步微任务,网上很多人都是写错了的,老师跟我们提到的,是宏任务优先级高于微任务,而网上所说的微任务先执行,那是因为上一轮的微任务执行完了,上一轮循环结束了,才能轮到下一轮的进行,从而开始执行宏任务

  • 事件循环

任务开启后内部执行时可能会有新任务

  • 宏任务:脚本js,整个代码就是一个宏任务

任务执行过程: 执行顺序 1、所有任务都在主进程上执行,异步任务会经历2个阶段 Event Table和Event Queue

2、同步任务在主进程排队执行,异步任务(包括宏任务和微任务)在事件队列排队等待进入主进程执行

3、遇到宏任务推进宏任务队列,遇到微任务推进微任务队列

4、执行宏任务,执行完宏任务,检查有没有当前层的微任务。

5、继续执行下一个宏任务,然后执行对应层次的微任务,直到全部执行完毕。

console.log(1)

setTimeout(() => {

     console.log(2)

}, 0);

console.log(3);

new Promise((resolve) => {

    console.log(4);

    resolve();

    console.log(5)

}).then(() => {

    console.log(6);

});

console.log(7)

注:一个事件循环,但是任务队列可以有多个。

创建Promise实例时是同步的,故此1、3、4、5、7是同步执行的。

promise.then()、setTimeout都是异步执行的,都会在异步队列中等待执行。

而promise.then()异步会放到microtask queue(微任务队列)中,microtask queue队列中的内容是在当前脚本执行完毕后立即发生的事。所以当同步Promise创建实例完成后,就执行promise.then(),然后把setTimeout放入执行堆栈中执行,故先执行Promise,再执行setTimeout。

任务队列和事件循环

  • JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

  • 首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

  • 异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

  • JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环.

console.log("1");
setTimeout(function () {
	console.log("2");
}, 0);
console.log("3");
setTimeout(function () {
	console.log("5");
}, 0);
console.log("6");

ES6中的异步编程

可以看到2开始没有输,出因为它是异步任务,不在主程序而在任务队列,虽然定时器设置的是0秒后执行,但是在主程序执行完前它是不会执行的。而弹出提示框4是同步操作,没有点击确定前,会阻塞主程序里的任务执行。点击确定后继续执行主程序输出6,到此主程序执行完毕,开始执行任务队列的任务输出2和5。

转载自:https://juejin.cn/post/7119884175972237325
评论
请登录