js三座大山之异步二异步方案

269 阅读5分钟

js三座大山

一:函数式编程
js三座大山之函数1 
js三座大山之函数2-作用域与动态this

二:面向对象编程
js三座大山之对象,继承,类,原型链

三:异步编程:
js三座大山之异步一单线程,event loope,宏任务&微任务
js三座大山之异步二异步方案
js三座大山之异步三promise本质
js三座大山之异步四-Promise的同步调用消除异步的传染性
js三座大山之异步五基于异步的js性能优化
js三座大山之异步六实现微任务的N种方式
js三座大山之异步七实现宏任务的N种方式

在上一篇中我们知道 为了解决js单线程引入的事件循环机制。以及了解了什么是同步任务 异步任务 异步宏任务 异步微任务这些异步编程的基础概念。下面介绍下异步编程都有哪些实现方案。

回调

例如网络请求传入一个回调函数 执行数据获取后的逻辑处理

ajax(url, (res1) => {
  console.log(res1);
  // 处理逻辑
});

回调函数的主要问题是可能会导致回调地狱(Callback Hell),即回调函数嵌套回调函数,使得代码难以阅读和维护。

ajax(url, (res1) => {
  console.log(res1);
  // 处理逻辑
  ajax(url1, (res2) => {
    console.log(res2);
    setTimeout(() => {
      // 处理逻辑
      ajax(url2, (res3) => {
        // 处理逻辑
        console.log(res3);
      });
    }, 2000);
  });
});

基于事件的发布订阅

先注册一系列回调事件 当异步任务完成 触发回调函数 执行操作。

Event.subscribe('key', fn1)
Event.subscribe('key', fn2)


ajax(url, (res1) => {
  Event.publish('key', res1)
})


class Event{
  store=new Map();
  subscribe(name, callback){
      if(!store[name]){
        const s = new Set();
        s.add(callback)
        this.store.set(name, s)
        return;
      }
      store[name].add(callback)
  };
  unSubscribe(name, callback){
    if(!store[name]) return;
    store[name].delete(callback)
  }
  publish(name, data){
    if(!store[name]) return;
    store[name].forEach(fn=>fn(data))
  }
}

promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise 对象就是为了解决回调地狱而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。

// 第一个例子改为promise实现

ajax(url)
  .then((res1) => {
    console.log(res1);
    // 处理逻辑
    return ajax(url1);
  })
  .then((res2) => {
    console.log(res2);
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 2000);
    });
  })
  .then(() => {
    return ajax(url2);
  })
  .then((res3) => {
    // 处理逻辑
    console.log(res3);
  });

可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

生成器Generators/yield:

Generator 最大的特点就是可以交出函数的执行权,可暂停可恢复

有以下三个应用场景:

1. 生成迭代器

const obj = {};
[...obj] // 报错 对象不可迭代

const obj = {
    [Symbol.iterator]:function* (){
        let i = 0;
        while(i< 10){
            yield i;
            i++;
        }
    }
};
[...obj] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2. 惰性求值

yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能.

function* gen() {
  yield  123 + 456;
}

const g = gen() // 并不会计算求值
g.next() // 求值获得结果

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

3. 异步的解决方案

JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。

这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。

Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。

function* load(){
    console.log('loading');
    const data = yield setTimeout(()=>{
        it.next(123)
    }, 2000)
    console.log('get result', data)
}
var it = load();
it.next();
function action(){
    console.log('other action ')
}
action();

结果
// loading
// other action 
// get result 123

截屏2023-12-26 下午4.32.40.png

分析步骤:

  1. 解析脚本 load函数入栈 执行返回一个迭代器 调用第一次next后 打印loading 运行到yield语句 调用异步定时器 load函数被暂停 出栈并推入到冻结区域 执行栈为空
  2. 异步定时器任务被执行
  3. action函数入栈 action执行打印other action 执行结束 action出栈并销毁
  4. 异步定时器执行结束 异步任务进入任务队列
  5. 执行栈 运行任务队列的回调任务 callback入栈 callback调用了load
  6. load被恢复到执行栈 load继续执行 打印get result 123 结束后出栈并销毁
  7. callback执行结束 出栈销毁

await/async

await/async是基于promise的,是 Generator 函数的语法糖,将 Generator 函数做了进一步的封装 内部实现了自动调用next 直至拿到最终返回结果。相比于Generator, await/async有更好的语义,更贴近同步的方法实现了异步调用。是目前主导的异步实现方案

async function load(){
    console.log('loading');
    const data = await new Promise((resolve)=>{
        setTimeout(()=>{
        resolve(123)
      }, 2000);
    })
    console.log('get result', data)
}

load();
function action(){
    console.log('other action ')
}
action();

结果
// loading
// other action 
// get result 123

参考

  1. 阮一峰promise
  2. generator-async
  3. developer.mozilla.org/en-US/docs/…