文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

总结5种JavaScript异步解决方案

2023-05-15 08:09

关注

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异步内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯