教程
异步解决方案补充
2074

简述

接着昨天讲,四种异步解决方案前两种回调函数和promise昨天讲过了,今天只是补充说明另外两种解决方案。那讲之前肯定少不了我们要明确一下同步和异步的概念,这里咱找了一个更官方的解释来让我们更加的通俗易懂。

所谓同步(synchronization),简单来说,就是顺序执行,指的是同一时间只能做一件事情,只有目前正在执行的事情做完之后,才能做下一件事情。比如咱们去火车站买票,假设窗口只有1个,那么同一时间只能处理1个人的购票业务,其余的需要进行排队。这种one by one的动作就是同步。同步操作的优点在于做任何事情都是依次执行,井然有序,不会存在大家同时抢一个资源的问题。同步操作的缺点在于会阻塞后续代码的执行。如果当前执行的任务需要花费很长的时间,那么后面的程序就只能一直等待。从而影响效率,对应到前端页面的展示来说,有可能会造成页面渲染的阻塞,大大影响用户体验。

所谓异步(Asynchronization),指的是当前代码的执行不影响后面代码的执行。当程序运行到异步的代码时,会将该异步的代码作为任务放进任务队列,而不是推入主线程的调用栈。等主线程执行完之后,再去任务队列里执行对应的任务即可。因此,异步操作的优点就是不会阻塞后续代码的执行

另外两种方案

1. Generator

Generator是ES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦,因此ES6推出了generator,用于更方便的创建iterator。也就是说,Generator就是一个返回值为iterator对象的函数。

这时又蒙蔽了,iterator又他妈是什么?

iterator中文名叫迭代器。它为js中各种不同的数据结构(Object、Array、Set、Map)提供统一的访问机制。任何数据结构只要部署了Iterator接口,就可以完成遍历操作。 因此iterator也是一种对象,不过相比于普通对象来说,它有着专为迭代而设计的接口。

iterator 的作用:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列;
  • ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费

原生具备iterator接口的数据结构如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数里的arguments对象
  • NodeList对象

这些数据结构都有一个Symbol.iterator属性,可以直接通过这个属性来直接创建一个迭代器。也就是说,Symbol.iterator属性只是一个用来创建迭代器的接口,而不是一个迭代器,因为它不含遍历的部分。
使用Symbol.iterator接口生成iterator迭代器来遍历数组的过程为:

let arr = ['a','b','c'];

let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

for ... of的循环内部实现机制其实就是iterator,它首先调用被遍历集合对象的 Symbol.iterator 方法,该方法返回一个迭代器对象,迭代器对象是可以拥有.next()方法的任何对象,然后,在 for ... of 的每次循环中,都将调用该迭代器对象上的 .next 方法。然后使用for i of打印出来的i也就是调用.next方法后得到的对象上的value属性。

对于原生Object不具备iterator接口的数据结构,我们可以采用自定义的方式来创建一个遍历器

      let obj = { a: "hello", b: "world" };
      // 自定义迭代器
      function createIterator(items) {
        let keyArr = Object.keys(items);
        let i = 0;
        return {
          next: function () {
            let done = i >= keyArr.length;
            let value = !done ? items[keyArr[i++]] : undefined;
            return {
              value: value,
              done: done,
            };
          },
        };
      }

      let iterator = createIterator(obj);
      console.log(iterator.next()); // "{ value: 'hello', done: false }"
      console.log(iterator.next()); // "{ value: 'world', done: false }"
      console.log(iterator.next()); // "{ value: undefined, done: true }"

理解了迭代器,我们终于可以来聊聊Generator了:

Generator 函数是 ES6 提供的一种异步编程解决方案。形式上,Generator 函数是一个普通函数,但是有两个特征:

  • function关键字与函数名之间有一个星号
  • 函数体内部使用yield语句,定义不同的内部状态

Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object

function* generator() {
    yield 1
    yield 2
};
let iterator = generator();
iterator.next()  // {value: 1, done: false}
iterator.next()  // {value: 2, done: false}
iterator.next()  // {value: undefined, done: true}

使用Generator的其余注意事项:

  • yield表达式只能用在 Generator 函数里面
  • yield表达式如果用在另一个表达式之中,必须放在圆括号里面
  • yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
  • 如果 return 语句后面还有 yield 表达式,那么后面的 yield 完全不生效
  • 箭头函数不能用做 generator
  • 需要注意的是,yield 不能跨函数。并且yield需要和*配套使用,别处使用无效

讲了这么多,那么Generator到底有什么用呢?看看下面例子

      function* gen() {
        const result = yield fetch("https://api.q6q.cc/blog");
        console.log(result);
      }

      let g = gen();
      const result = g.next().value;
      result
        .then(function (data) {
          return data.json();
        })
        .then(function (data) {
          g.next(data);
        });
  • 因为Generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。
  • Generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个在ajax请求中很有用,避免了回调地狱.

2. async/await

async/await是ES7提出基于Promise的异步的终极解决方案。async函数其实是把promise包装了一下。使用async函数可以让代码简洁很多,不需要promise一样需要些then,不需要写匿名函数处理promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码

//原来这么写
fn1(10)
        .then((n) => fn2(n))
        .then((n) => fn3(n))
        .then((n) => fn4(n))
        .then((n) => console.log(n))
        .catch((e) => console.log(e));

虽然相比于回调地狱来说,链式调用确实顺眼多了。但是其呈现仍然略繁琐了一些。 而async/await的出现,就使得我们可以通过同步代码来达到异步的效果

//现在这样
async function addAll() {
        const count1 = await fn1(10);
        const count2 = await fn2(count1);
        const count3 = await fn3(count2);
        const count4 = await fn4(count3);
        console.log(count4);
      }
     addAll();

简单发送异步请求,处理返回结果

async function getBlogMsg() {
        return await fetch("https://api.q6q.cc/blog").then((resp) =>
          resp.json()
        );
      }

      let result = getBlogMsg();
      result
        .then(
          ({
            data: {
              theme_msg: { article_category },
            },
          }) => {
            console.log(article_category);
          }
        )
        .catch((err) => console.log(err));

由此可见,相比generator那又臭又长的迭代器而言,虽然说和各有各的好处,但是Promise搭配async/await的使用才是异步处理最方便最优解!


  • 上一篇
  • 下一篇
  • 添加评论
    评论(14)
    irils
    irils

    强仔
    小赵同学

    牛逼 大佬

    强仔
    上官
    上官

    这啥主题 在哪下载

    上官
    上官

    这啥主题 在哪下载

    强仔
    上官
    上官

    这啥主题 在哪下载

    乌拉
    乌拉

    Rose
    Rose

    以前跟着公开课抄代码的时候,见过async,当时还不知道具体是什么

    强仔
    Rose
    Rose

    袁某人

    看不懂,我只会用ajax,因为网上教程多

    welcome to qiangzai blog