1. 异步编程

1.1. 内容概要

  • 同步模式与异步模式
  • 事件循环与消息队列
  • 异步编程的几种方式
  • Promise 异步方案,任务队列
  • Generator 异步方案, async,await 语法糖

1.1.1. Promise

  • 每一个 then 方法实际上都是为上一个 then 方法返回的 Promise 对象添加状态明确后的回调,then 的回调中可以手动返回 Promise 对象,如果 then 中的回调返回的是普通的值,会作为当前 then 方法返回的 Promise 的值 传入后一个 then 方法中的回调,如果没有返回值,返回 undefined
  • then 方法返回的是全新的 Promise 对象
  • 如果回调中返回的是 新的Promise 对象,后面的 then 方法回调会等待它的状态变更;也就是说后面的 then 方法是为前面返回的 Promise 对象注册了对应回调;但注意,此then方法回调中接收的值也就是新的Promise返回的值
  • new Promise 实例化的时候,p1 的 resolve 函数可传入另外如 p2 的 promise 实例,resolve(p2).此时 p2 的状态决定了 p1 的状态,即使说 p2 会把状态传给 p1,如果 p2 状态为 fulfilled,p1 的 then 中回调函数会立即执行
  • 当then方法传递一个普通类型的数据而不是回调函数的时候,此时链条中的值不会发生变化 如Promise.resolve(1).then(2).then(console.log)仍为1

1.1.2. 异常处理

  • 在 then 方法第二个参数传入回调函数
  • 通过 catch 方法
function ajax(url) {
  return new Promsie((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.responseType = "json";
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    xhr.send();
  });
}

ajax("/url").then(
  function (value) {},
  function (error) {
    // 只是给第一个Promise(ajax)对象指定的异常回调
    // 捕获不到上面then中的异常
  }
);

ajax("/url")
  .then(function (value) {}) // 返回了Promise对象
  .catch(function (error) {
    // 实际上是在给前面then方法返回的Promise对象指定失败回调,并不是给第一个Promise指定,只是因为promise链条上,异常会一直传递
  });

1.1.3. Promise 静态方法

Promise.resolve()快速的把一个值转换为 Promise 对象, 会把值包裹在Promise对象中,把创建出来的Promise对象作为Promise.resolve()的返回值

Promise.resolve("foo");
// 等价于
new Promise(function (resolve, reject) {
  resolve("foo");
});

通过 Promise.resolve()包装一个 Promise 对象,得到的是相同的 Promise 对象

var p = ajax("/api/...");
p === Promise.resolve(p); // 如果是另外一个写法,不相等

1.1.4. 并行执行

Promise.all([])

只有多个 Promise 都成功结束才会成功结束,其中有返回失败结束,整体就会返回失败结束

Promise.race()

只会等待第一个任务结束

1.1.5. Promise 执行时序

Promise 的回调会作为微任务执行; 目前绝大多数异步调用都是作为宏任务执行,Promise, MutationObserver,node 中的 process.nextTick 作为微任务执行

1.2. Generator 异步方案

Promise 的缺点

仍然会有大量的回调函数,虽然没有嵌套,但是达不到传统同步代码形式的直观

funntion * foo () {
  try {
    const res = yield 'foo'   // yield 关键字向外返回一个值, res === 'bar';暂停函数执行,直到下一次调用生成器对象的next方法再往下执行
    console.log(res)    // 如果next传入参数,会作为yield返回值
  } catch (error) {
    console.log(error)
  }
}
const generator = foo()
const res = generator.next('bar')    // { value: 'foo', done: false }

generator.throw(new Error('error'))

generator.throw 会对生成器函数内部抛出异常,内部能进行捕获

1.2.1. 实现异步编程

function* main() {
  const users = yield ajax("/api/...");

  const posts = yield ajax("/api/...");
}

const g = main();
const result = g.next();    // 返回上述第一个yield定义的Promise对象

result.value.then((data) => {
  const result2 = g.next(data);     // users
  if (result2.done) return; // 思考: 此处可用递归
  result2.value.then(
    (data1) => {
      g.next(data);     // posts
    },
    (error) => {
      g.throw(error); // 此时可以在main方法中通过try catch捕获异常
    }
  );
});

递归方式

function handleResult(result) {
  if (result.done) return;
  result.value.then((data) => {
    handleResult(g.next(data));
  });
}

对生成器函数的封装

function co(generator) {
  const g = generator();
  function handleResult(result) {
    if (result.done) return;
    result.value.then((data) => {
      handleResult(g.next(data));
    });
  }
  handleResult(g.next())
}

// https://github.com/tj/io

1.2.2. async/await

提供扁平化的异步编程体验,是生成器的语法糖,语言层面的的标准异步编程语法

  • 把生成器函数修改为带有async关键字的函数,yield关键字修改为await即可
  • 调用async函数会返回Promise对象

async会把函数调用结果包装成为一个Promise,如

async funciton fn() {
  new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('time end')
      resolve()
    }, 2000)
  })
}

console.log(fn)   // Promise {<fulfilled>: undefined}

注意async可单独使用,如上,此时调用函数状态立即为fulfilled

使用await关键字后,包装后的Promise实例的状态根据await后的Promise resolve或reject而改变相应状态,如

async funciton fn() {
  await new Promise((resolve, reject) => {    // await..., return ... ,return await...  效果相同, 或者去掉async,直接return
    setTimeout(() => {
      console.log('time end')
      resolve()
    }, 2000)
  })
}

console.log(fn)   // Promise {<pending>: undefined}
var res = fn()
setTimeout(() => {
  console.log(res)    // Promise {<fuifilled>: undefined}
}, 3000)

想一想会很清晰,await后的Promise状态会传递给外层,联系到开头的第二点,回调中返回了另外一个Promise,后面的 then 方法回调会等待返回的这个Promise的状态变更,因此就能理解上面代码的效果

另外关于错误抛出的一个场景:

async mounted () {
  this.chart.showLoading()
  await this.getInfo()
  console.log('hide')
  this.chart.hideLoading()
}

async getInfo () {
  await new Promise((resolve, reject) => {
    setTimeout((resolve, reject) => {
      reject('error')
    },100)
  }).then(res => {
    console.log(res)
  })
}

这里我们用定时器可以模拟网络操作,此时你会发现hide无法打印,后面的代码都不会执行.这是因为某个耗时操作中出了错,且我们没有捕获,解决这个问题很简单

要么then后面调用catch,即使不做任何处理,也要调用,或者try...catch包裹getInfo函数

因此,关于错误的捕获原则

  • 永远要处理promise的rejected,监听unhandledRejection只是最后一道防线
  • 处理stream流要处理错误
  • 处理*sync,JSON.parse时,一定要用try...catch包裹,或者放入promise.且如果try...catch中还有try...catch, 则内层catch中的错误要throw出来被外层catch捕获
Jason Huang all right reserved,powered by Gitbook该文件最后修改时间: 2021-01-27 15:45:59

results matching ""

    No results matching ""