js 异步编程的4种方式

背景

js 语言执行环境——单线程(Single Thread)

单线程

一次只能执行一个任务,超过一个的就只能排队

单线程优势

  • 实现简单

  • 执行环境单纯

单线程劣势

存在耗时很长的任务会拖慢整个程序的运行

单线程带来的问题

浏览器无响应(假死)

浏览器假死原因分析

某段 js 代码(某个任务)长时间运行(比如死循环)

js 对于单线程劣势的解决方案

执行模式分为:同步(Synchronous)和异步(Asynchronus)

同步模式
  • 后一个任务等待前一个任务结束

  • 程序执行顺序同任务排列顺序

异步模式
  • 每个任务有 1 个或多个回调函数(callback)

  • 前一个任务结束,不是继续执行后一个任务,而是执行回调

  • 后一个任务不等前一个任务结束就执行

  • 程序的执行顺序和任务排列顺序不一致

异步模式使用

浏览器端耗时长的任务都应该异步执行

避免浏览器失去效应

异步模式实践

Ajax

异步编程的 4 种方式(实践)

1.回调函数(最基本)

实际场景

两个函数 f1()、f2(),f1()耗时长

解决方案

把 f2()函数写成 f1()函数的回调函数

function f1(callback) {
  setTimeout(function () {
    // code of f1
    callback();
  }, 1000);
}

f1(f2);
  • f1 不会阻塞程序执行

  • 先执行程序主逻辑,耗时操作推迟执行

回调函数存在的问题
  • 不利于代码阅读和维护

  • 各部分间耦合高(Coupling)

  • 流程变乱

  • 每个任务仅支持一个回调函数

2.事件监听(信号)

使用事件驱动模式——任务的执行不取决于代码顺序,而是事件的发生

为 f1 绑定一个事件

f1.on("done", f2);

解释:当 f1 发生 done 事件时执行 f2

function f1() {
  setTimeout(function () {
    f1.trigger("done");
  }, 1000);
}

f1.trigger(‘done’)表示当执行完其上的内容,立即触发 done 事件(即执行 f2)

优势

  • 容易理解

  • 每个事件可指定多个回调函数

  • 可以去耦合(decoupling)

  • 有利于实现模块化

缺陷

  • 整个程序变成事件驱动

  • 运行流程不清晰

3.发布/订阅模式 或 观察者模式(publish/subscribe pattern or observer pattern)

制造一个信号中心,某任务完成后向信号中心**发布(publish)一个信号,其他任务可向信号中心订阅(subscribe)**这个信号,从而得知自己什么时候执行

该模式的多种实现

参考:https://msdn.microsoft.com/en-us/magazine/hh201955.aspx

这里举例第一种,是 jQuery 的一个插件

  1. f2 向 jQuery(信号中心)订阅 done 信号
jQuery.subscribe("done", f2);
  1. f1 向 jQuery(信号中心)发布 done 信号
function f1() {
  setTimeout(function () {
    jQuery.publish("done");
  });
}

如果 f2 只要执行一次,可以在执行后取消订阅

jQuery.unsubscribe("done", f2);

发布订阅模式优劣分析

与事件监听类似,但可通过信号中心清晰了解到存在多少信号(发布者发布了多少信号)、每个信号有多少订阅者,从而监控程序的执行

4.Promises 对象

Promises 对象是 CommonJS 工作组提出的一种规范,目的旨在为异步编程提供统一的接口

Promises 实现异步编程的思想

  • 每一个异步任务返回一个 Promise 对象

  • 该对象有一个then方法,允许指定回调函数

实例操作

为 f1 指定回调函数为 f2

f1().then(f2);

jQuery 的实现

function f1() {
  var dfd = $.Deferred();
  setTimeout(function () {
    dfd.resolve();
  }, 1000);
  return dfd.promise;
}
优点分析
  • 回调函数变成了链式写法,程序流程清晰

  • 有配套的整套方法,可实现强大的功能

应用场景
指定多个回调函数
f1().then(f2).then(f3);
指定发生错误时的回调函数
f1().then(f2).fail(f3);
Promises 使用的优缺点
优点
  • 一个任务已完成,再添加回调函数,该回调函数会立刻执行

  • 不用担心是否错过某个事件信号

缺点
  • 难以理解和编写

参考链接: