zl程序教程

您现在的位置是:首页 >  工具

当前栏目

异步解决方案补充

2023-06-13 09:12:44 时间

简述

接着昨天讲,四种异步解决方案前两种回调函数和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的使用才是异步处理最方便最优解!