总结5种JavaScript异步解决方案

 更新时间:2023年04月10日 10:29:34   作者:技术小张zz  
这篇文章主要讲解了JavaScript异步解决处理方案,文章中有详细的解决方案和代码示例,需要的朋友参考一下

1.回调

回调简单地理解为一个函数作为参数传递给另一个函数,回调是早期最常用的异步解决方案之一。

回调不一定是异步的,也不直接相关。

举个简单的例子:

function f1(cb) {
  setTimeout(() => {
    cb && cb();
  }, 2000);
}
 
f1(() => {
  console.log("1");
});

如上,我们在函数f1中使用setTimeout模拟一个耗时2s的任务,在耗时任务结束时抛出回调,这样我们就可以调用它,让回调函数在耗时结束时执行函数 f1 中的任务。

这样,我们就把同步操作变成了异步操作。f1不会阻塞程序,相当于先执行程序的主要逻辑,推迟执行耗时操作。

回调的优点和缺点

优点:简单,容易理解。

缺点:代码不优雅,可读性差,不易维护,耦合度高,层层嵌套造成回调地狱。

2.事件监听(发布订阅模式)

发布-订阅模式定义了对象之间一对多的依赖关系,这样当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。

我们都使用过发布-订阅模式,例如,如果我们将事件函数绑定到 DOM 节点。

document.body.addEventListener('click', function () {
  console.log('click');
})

但这只是发布-订阅模式最简单的使用,在很多场景下我们往往会使用一些自定义事件来满足我们的需求。

有很多方法可以实现发布-订阅模式,所以这里有一个使用类的简单实现。

class Emitter {
  constructor() {
    // _listener array, key is the custom event name, value is the execution callback array - as there may be more than one
    this._listener = []
  }
 
  // 订阅 监听事件
  on(type, fn) {
    // Determine if the event exists in the _listener array.
    // Exists to push the callback to the value array corresponding to the event name, does not exist to add directly
    this._listener[type] 
      ? this._listener[type].push(fn) 
     : (this._listener[type] = [fn])
  }
 
  // Publish Trigger Event
  trigger(type, ...rest) {
    // Determine if the trigger event exists
    if (!this._listener[type]) return
    // Iterate through the array of callbacks executing the event and pass the parameters
    this._listener[type].forEach(callback => callback(...rest))
  }
}

如上所示,我们创建了一个 Emitter 类,并在和触发器上添加了两个原型方法,使用如下。

// Create an emitter instance
const emitter = new Emitter()
 
emitter.on("done", function(arg1, arg2) {
  console.log(arg1, arg2)
})
 
emitter.on("done", function(arg1, arg2) {
  console.log(arg2, arg1)
})
 
function fn1() {
  console.log('I am the main program')
  setTimeout(() => {
    emitter.trigger("done", "Asynchronous parameter I", "Asynchronous parameter II")
  }, 1000)
}
 
fn1()

我们先创建一个emitter实例,然后注册事件,然后触发事件,这样也解决了异步问题。

事件监听的优点和缺点

优点:更符合模块化思想,我们在编写自己的监听器的时候可以做很多优化,从而更好的监听程序的运行。

缺点:整个程序变成了事件驱动,或多或少影响了流程,而且每次使用都要注册事件监听器然后触发,比较麻烦,代码也不是很优雅。

3.Promise

ES6 标准化并引入了 Promise 对象,这是一种异步编程的解决方案。

简单的说,就是用同步的方式写异步代码,可以用来解决回调地狱问题。

Promise对象的状态一旦改变,就不会再改变,只有两种可能的改变。

  1. 由待定改为已解决。
  2. 由Pending改为Rejected。

我们使用 setTimeout 来模拟异步操作。

function analogAsync(n) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(n + 500), n);
  });
}
 
function fn1(n) {
  console.log(`step1 with ${n}`);
  return analogAsync(n);
}
 
function fn2(n) {
  console.log(`step2 with ${n}`);
  return analogAsync(n);
}
 
function fn3(n) {
  console.log(`step3 with ${n}`);
  return analogAsync(n);
}

使用 Promise 来实现。

function fn() {
  let time1 = 0;
  fn1(time1)
    .then((time2) => fn2(time2))
    .then((time3) => fn3(time3))
    .then((res) => {
      console.log(`result is ${res}`);
    });
}
 
fn();

Promise 优点和缺点

优点:Promise以同步的方式编写异步代码,避免了回调函数层层嵌套,可读性更强。链式操作,可以在then中继续写Promise对象并return,然后继续调用then进行回调操作。

缺点:Promise对象一旦创建就会立即执行,不能中途取消。如果没有设置回调函数,Promise 会在内部抛出错误,不会向外流。

4.Generator

Generator其实就是一个函数,只不过是一个特殊的函数。Generator 的特别之处在于它可以中途停止。

function *generatorFn() {
  console.log("a");
  yield '1';
  console.log("b");
  yield '2'; 
  console.log("c");
  return '3';
}
 
let it = generatorFn();
it.next();
it.next();
it.next();
it.next();

上面的示例是一个具有以下特征的生成器函数。与普通函数不同,Generator 函数在函数之后和函数名称之前有一个 *,该函数有一个内部 yield 字段,函数调用后的返回值使用next方法。

Generator的优点和缺点

优点:优雅的流程控制方法,允许函数被中断地执行。

缺点:Generator函数的执行必须依赖executor,对于只做异步处理还是不太方便。

5.async/await

ES2017标准引入了async函数,使得异步操作更加方便。async是异步的意思,await是async wait的简写,也就是异步等待。async/await 被许多人认为是 js 中异步操作的终极和最优雅的解决方案。

异步在做什么?

async 函数返回一个 Promise 对象。如果直接在 async 函数中返回一个直接量,async 会通过 Promise.resolve() 将直接量包装在一个 Promise 对象中。

await 是什么?

await 是一个表达式,其计算结果为 Promise 对象或其他值(换句话说,没有特殊限定,无论如何)。

如果 await 后面没有跟 Promise 对象,则直接执行。

如果 await 后面跟着一个 Promise 对象,它会阻塞后面的代码,Promise 对象解析,然后获取 resolve 的值作为 await 表达式的结果。

await 只能在异步函数中使用

上面使用setTimeout来模拟异步操作,我们使用async/await来实现。

async function fn() {
  let time1 = 0;
  let time2 = await fn1(time1);
  let time3 = await fn2(time2);
  let res = await fn3(time3);
  console.log(`result is ${res}`);
}
 
fn();

输出结果和上面的 Promise 实现是一样的,但是 async/await 的代码结构看起来更清晰,几乎和同步写法一样优雅。

async/await的优点和缺点

优点:内置执行器,语义更好,适用性更广。

缺点:误用 await 可能会导致性能问题,因为 await 会阻塞代码。

到此这篇关于总结5种JavaScript异步解决方案的文章就介绍到这了,更多相关解决JavaScript异步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • javascript 三组文字间隙滚动实例代码

    javascript 三组文字间隙滚动实例代码

    非常实用的文字间隙滚动效果代码
    2008-06-06
  • Bootstrap CSS布局之代码

    Bootstrap CSS布局之代码

    这篇文章主要介绍了Bootstrap CSS布局之代码的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • js实现特定位取反原理及示例

    js实现特定位取反原理及示例

    循环输入每组两个数hex和n(0<=n<31),hex是一个16进制的数字,我们要做的是将hex的第n位取反,然后以16进制的形式输出对应的结果
    2014-06-06
  • javascript数组遍历的方法实例分析

    javascript数组遍历的方法实例分析

    这篇文章主要介绍了javascript数组遍历的方法,结合实例形式分析了javascript数组遍历及相关的some、every、filter、map等方法的使用技巧,需要的朋友可以参考下
    2016-09-09
  • JavaScript+html实现前端页面滑动验证(2)

    JavaScript+html实现前端页面滑动验证(2)

    这篇文章主要为大家详细介绍了JavaScript+html实现前端页面滑动验证的第二种方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • ionic2打包android时gradle无法下载的解决方法

    ionic2打包android时gradle无法下载的解决方法

    这篇文章主要为大家详细介绍了ionic2打包android时gradle无法下载的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • JavaScript实现简单动态进度条效果

    JavaScript实现简单动态进度条效果

    这篇文章主要为大家详细介绍了JavaScript实现简单动态进度条效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • JS target与currentTarget区别说明

    JS target与currentTarget区别说明

    target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是一样的,而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(一般为父级)。
    2011-08-08
  • 微信小程序新手教程之页面打开数量限制

    微信小程序新手教程之页面打开数量限制

    这篇文章主要给大家介绍了关于微信小程序新手教程之页面打开数量限制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • javascript+css3开发打气球小游戏完整代码

    javascript+css3开发打气球小游戏完整代码

    这是一个简单但是印象深刻的小游戏,打气球小游戏的实现代码,主要基于js和css3,基于css3画气球,具体实现代码大家参考下本文
    2017-11-11

最新评论