JS异步编程的实现方式有哪些?

参考:
什么叫异步
JS异步编程 (2) - Promise、Generator、async/await
JS 异步编程六种方案

1. 回调函数模式

回调函数是异步操作最基本的方法。按照正常的JS处理机制,同步操作一定在异步操作之前,若想改变顺序,最简单的方式就是采用回调的方式处理:

function asyncFn(callback) {
    setTimeout(() => {
        console.log('asyncFn');
        callback();
    }, 0)
}
function normalFn() {
    console.log('normalFn');
}

asyncFn(normalFn);
  • 缺点
    • 容易出现回调地狱
    • 当多个回调函数嵌套,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪
    • 不能使用 try catch 不能直接return
  • 优点
    • 简单、容易理解和实现
2. 事件触发模式

另一种思路是采用事件驱动模式。即异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
比如一个我们注册一个按钮的点击事件或者注册一个自定义事件,然后通过点击或者trigger的方式触发这个事件。

var btn = document.getElementById('Button');
btn.onclick = function() {
    console.log('展示异步操作');
}

f1.on('done', f2) // 当f1发生done事件,就执行f2

function f1() {
    setTimeout(function () {
        f1.trigger('done');   // 执行完成后,立即触发done事件,从而开始执行f2
    }, 1000)
}
  • 缺点
    • 整个程序变成事件驱动型,运行流程不清晰
  • 优点
    • 可以绑定多个事件,每个事件可以指定多个回调函数
    • 可以“去耦合”,有利于实现模块化
3. 发布订阅

我们假定存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就是“发布/订阅”模式,也叫“观察者模式”。

  • f2向“信号中心”jQuery订阅“done”信号:
jQuery.subscribe("done", f2);   //  一旦done信号被发布,f2就会被执行
function f1() {
    setTimeout(() => {
        // f1的任务代码
        jQuery.publish("done");   // f1执行完之后,向“信号中心”jQuery发布“done”信号,从而引发f2的执行
    }, 1000)
}
  • 优点
    • 可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行
4. promise

每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如f1的回调函数f2:

f1().then(f2);
var p = new Promise((resolve, reject) => {
    // 这里是你的异步操作
    setTimeout(() => {
        resolve(1);
        reject();
    }, 1000)
})

p.then(val => {
    console.log(val);  // resolve函数里传的值
},
err => {
    console.log('reject'); // reject
})

例子:
(1)异步加载图片

function loadImageAsync(url) {
      return new Promise(function(resolve, reject) {
          const image = new Image();
          image.onload = function() {
              resolve(image);
          };
          image.onerror = function() {
              reject(image);
          };
          image.src = url;
      });
  }

(2)Ajax操作

const getJSON = function(url) {
    const p = new Promise((resolve, reject) => {
        const handler = function() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
        const client = new XMLHttpRequest();
        client.open('GET', url);
        client.onreadystatechange = handler;
        client.responseType = 'json';
        client.setRequestHeader('Accept', 'application/json');
        client.send();
    });
    return p;
};

getJSON('/posts.json').then(json => {
    console.log('Contents: ' + json);
}, error => {
    console.log('出错了', error)
})
5. Generator/yield

Generator最大的特点就是可以控制函数的执行

  • Generator函数是一个遍历器对象生成函数,也是一个状态机,封装了多个内部状态
  • 可暂停函数,yield可暂停,next可启动,每次返回的是yield后的表达式结果
  • yield表达值本身没有返回值(返回undefined),next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

分析:

  • Generator函数返回一个迭代器
  • 第一次执行next,无参数传入,函数暂停在yield(x+1)处,返回5 + 1 = 6;
  • 第二次执行next,传入参数12作为上一个yield表达式的返回值,函数暂停在y/3处,返回(2*12 / 3)= 8;
  • 第三次执行next,传入参数13作为上一个yield表达式的返回值,z = 13, x = 5, y = 24; 相加为42
6. Async/Await

参考:理解 JavaScript 的 async/await
async 函数的含义和用法
Async/Await替代Promise的6个理由
JS 异步编程六种方案
async用于申明一个function是异步的,而await用于等待一个异步方法的完成。await只能出现在async函数中。其特点:

  • async/await是基于promise实现的,不能用于普通的回调函数
  • async/await与promise一样,是非阻塞的
  • async/await使异步代码看起来像同步代码

同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。在没有await的情况下执行async函数,它会立即执行,返回一个Promise对象,并且绝不会阻塞后面的语句。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

且,await后面可以接普通函数调用或者直接量

  • 如果await等到的不是一个Promise对象,那么await表达式的运算结果就是它等到的东西
  • 如果等到的是一个Promise对象,await就忙起来了,它会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果

== 注意:== await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。

function getSomething() {
  return 'something';
}
async function testASync() {
  return Promise.resolve("hello async");
}
async function test() {
  const v1 = await getSomething();
  const v2 = await testASync();
  console.log(v1, v2);
}
test();

例子:指定多少毫秒后输入一个值

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);
(1) async相比于Generator函数的改进

async函数的实现,实际上就是将Generator函数和自动执行器,包装在一个函数里。

  1. 内置执行器。Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
  2. 更好的语义。async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
(2) async相比于promise
  1. 代码更简洁。我们不需要写.then,不需要写匿名函数处理 Promise 的 resolve 值,也不需要定义多余的 data 变量等等
  2. async/await 让 try/catch 可以同时处理同步和异步错误

但是,因为await将异步代码改造成同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低,代码没有依赖性的话,完全可以使用Promise.all

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值