JavaScript 异步编程

单线程的优势和弊端

js是单线程的,在浏览器中js的执行栈跟渲染线程是相互阻塞的。
单线程模式最大的优势就是更安全,更简单
缺点也很明确,就是如果中间有一个特别耗时的任务,其他的任务就要等待很长的时间,出现假死的情况。
为了解决这种问题,js有两种任务的执行模式:同步模式(// callback就是回调函数 // 就是把函数作为参数传递,缺点是不利于阅读,执行顺序混乱。 function foo(callback) { setTimeout(function(){ callback() }, 3000) } foo(function() { console.log('这就是一个回调函数') console.log('调用者定义这个函数,执行者执行这个函数') console.log('其实就是调用者告诉执行者异步任务结束后应该做什么') })

Promise 异步方案

Promise概述

回调的多重嵌套,会导致代码可读低、编写费劲、容易出错,故而被称为 callbJavaScript 异步编程

Promise基本用法

返回resolve

const promise = new Promise((resolve, reject) => {   resolve(100) })  promise.then((value) => {   console.log('resolved', value) // resolve 100 },(error) => {   console.log('rejected', error) }) 

返回reject

const promise = new Promise((resolve, reject) => {   reject(new Error('promise rejected')) })  promise.then((value) => {   console.log('resolved', value) },(error) => {   console.log('rejected', error)   // rejected Error: promise rejected   //  at E:professerlagouPromisepromise-example.js:4:10   //  at new Promise (<anonymous>) }) 

即便promise中没有任何的异步操作,then方法的回调函数仍然会进入到事件队列中排队。

Promise 的链式调用

  • promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

Promise异常处理

then中回调的onRejected方法

也就是then中的第二个回调函数

.catch()(推荐)

如果then中没有传入第二个回调 那么异常会进入catch的回调处理

promise中如果有异常,都会调用reject方法,还可以使用.catch()

使用.catch方法更为常见,因为更加符合链式调用

全局对象上的unhandledrejection事件

还可以在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,当然并不推荐使用

两个静态方法

Promise.resolve

  • 不带参数 返回一个Fulfilled状态的Promise对象 后面.then执行 then中的第一个回调
  • 参数为简单数据类型 返回一个Fulfilled状态的Promise对象 后面.then执行 then中的第一个回调,并且把该参数作为实参传入给回调
  • 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回
  • 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行
Promise.resolve('foo')   .then(function (value) {     console.log(value)   })  new Promise(function (resolve, reject) {   resolve('foo') })  // 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回  var promise = ajax('/api/users.json') var promise2 = Promise.resolve(promise) console.log(promise === promise2)  // 如果传入的是带有一个跟 Promise 一样的 then 方法的对象, // Promise.resolve 会将这个对象作为 Promise 执行  Promise.resolve({   then: function (onFulfilled, onRejected) {     onFulfilled('foo')   } }) .then(function (value) {   console.log(value) }) 

Promise.reject

Promise.reject()方法返回一个带有拒绝原因的Promise对象

// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由   Promise.reject(new Error('rejected'))   .catch(function (error) {      console.log(error)    })  Promise.reject('anything')   .catch(function (error) {     console.log(error)   })  

Promise 并行执行

.all()
  • Promise.all 方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。返回一个全新的Promise实例,它的状态由这三个promise实例决定 只有都fulfilled 新的Promise才会进入 fulfilled
.race()
  • Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数

Promise的执行时序

如何简单理解宏任务和微任务
比如:你在银行排队办理业务
那么队列中的每一个人都可以看做是一个宏任务
当排到你的时候 你告诉柜员你需要 办卡,存钱,转账,这些就是微任务。柜员不会让你办完一个业务就重新排一次队 而是一次性把你的微任务全处理完。然后才会轮到下一个人,也就是下一个宏任务。

宏任务 (macro) task

(macro) task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
宏任务包括

script(整体代码,同步代码) setTimeout setInterval I/O UI交互事件 postMessage MessageChannel setImmediate(Node.js 环境) 

微任务 (micro) task

(micro)task,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
微任务包括

Promise.then Object.observe MutaionObserver process.nextTick(Node.js 环境) 

判断代码执行结果

// 微任务  console.log('global start')  // setTimeout 的回调是 宏任务,进入回调队列排队 setTimeout(() => {   console.log('setTimeout') }, 0)  // Promise 的回调是 微任务,本轮调用末尾直接执行 Promise.resolve()   .then(() => {     console.log('promise')   })   .then(() => {     console.log('promise 2')   })   .then(() => {     console.log('promise 3')   })  console.log('global end')  

结果是:

// 同步代码看做是一次宏任务,执行结束后 执行该宏任务阶段产生的所有微任务 也就是promise中的then, 然后再执行下一个宏任务 global start global end promise promise 2 promise 3 setTimeout 

Generator 异步方案、Async/Await 语法糖

Generator 生成器函数

Generator 函数可以暂停执行和恢复执行。除此之外,它还有两个特性:函数体内外的数据交换和错误处理机制。
next方法不传递参数返回一个对象,next方法传入参数把参数传给函数内部
next 方法返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,这是向 Generator 函数体内输入数据。

function* gen(x){   var y = yield x + 2;   return y; }  var g = gen(1); g.next() // { value: 3, done: false } g.next(2) // { value: 2, done: true } 

上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值(3)。第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。因此,这一步的 value 属性,返回的就是2(变量 y 的值)。

Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。

function* gen(x){   try {     var y = yield x + 2;   } catch (e){      console.log(e);   }   return y; }  var g = gen(1); g.next(); g.throw'出错了'; // 出错了 

上面代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try … catch 代码块捕获。

Generator 配合 Promise 的异步方案在开发中 可以使用co这个库
当然, **“大人,时代变了”**我们现在用 async / await

Async/Await 语法糖

这个大家已经用的比较多了 就不再赘述了

基本用法

async function main () {   try {     const users = await ajax('/api/users.json')     console.log(users)     const posts = await ajax('/api/posts.json')     console.log(posts)     const urls = await ajax('/api/urls.json')     console.log(urls)   } catch (e) {     console.log(e)   } } 

异常处理

  • 让await后面的Promise对象自己catch
  • 也可以让外面的async函数返回的Promise对象统一catch
  • 像同步代码一样,放在一个try…catch结构中;
版权声明:玥玥 发表于 2021-03-16 7:44:53。
转载请注明:JavaScript 异步编程 | 女黑客导航