关注公众号“执鸢者”,回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频,回复“异步”获取本节整体思维导图。

本节主要阐述六种异步方案:回调函数、事件监听、发布/订阅、Promise、Generator和Async。其中重点是发布/订阅、Promise、Async的原理实现,通过对这几点的了解,希望我们前端切图仔能够在修炼内功的路上更进一步。

一、六种异步方案

1.1 回调函数

异步编程的最基本方法,把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
  • 优点:简单、容易理解和实现。
  • 缺点:多次调用会使代码结构混乱,形成回调地狱。
function sleep(time, callback) {    setTimeout(() => {        // 一些逻辑代码        callback();    }, time);}

1.2 事件监听

异步任务的执行不取决于代码的执行顺序,而取决于某个事件是否发生。
  • 优点:易于理解,此外对于每个事件可以指定多个回调函数,而且可以“去耦合”,有利于实现模块化。
  • 缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰。
dom.addEventListener('click', () => {    console.log('dom被点击后触发!!!');})

1.3 发布/订阅

发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。订阅者(观察者)把自己想要订阅的事件注册到调度中心,当该事件触发的时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。

1.4 Promise

Promise 是异步编程的一种解决方案,是为解决回调函数地狱这个问题而提出的,它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套改为链式调用。
  • 优点:将回调函数的嵌套改为了链式调用;使用then方法以后,异步任务的两端执行看的更加清楚。
  • 缺点:Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。
const promise = new Promise((resolve, reject) => {    if (/*如果异步成功*/) {        resolve(value);    } else {        reject(error);    }});promise.then((value) => {    // ...success}, (reason) => {    // ...failure})

1.5 Generator

Generator 函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。其最大特点是可以控制函数的执行。
  • 优点:异步操作表示的很简洁,此外可以控制函数的执行。
  • 缺点:流程管理不方便,不能实现自动化的流程管理。
function* genF() {    yield 'come on!';    yield 'Front End Engineer';    return 'goood';}const gF = genF();gF.next();// {value: "come on!", done: false}gF.next();// {value: "Front End Engineer", done: false}gF.next();// {value: "goood", done: true}gF.next();// {value: undefined, done: true}

1.6 Async

ES2017 标准引入了async函数,使得异步操作变得更加方便。简言之,该函数就是Generator函数的语法糖。
  • 优点:内置执行器,可以自动执行;语义相比Generator更加清晰;返回值是Promise,比Generator函数的返回值是Iterator对象操作更加方便。
  • 增加学习成本。
async function asyncFun() {    await func1()        await func2();        return '666';}function func1() {    return new Promise((resolve, reject) => {        setTimeout(() => {            resolve('888')        }, 100);    }).then((value) => {        console.log(value);    });}function func2() {    return new Promise((resolve, reject) => {        setTimeout(() => {            resolve('777')        });    }).then((value) => {        console.log(value);    });}asyncFun().then((value) => {    console.log(value);});// 888// 777// 666

二、Promise原理实现

不管是实际开发中还是面试过程中,各位老铁们对Promise肯定不会陌生,下面就让我们一起来唠一唠Promsie的实现原理,根据PromiseA+规范来进行实现,然后对其相关的静态方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和实例方法(Promise.prototype.catch()、Promise.prototype.finally())进行实现。

2.1 思考一下

首先用一幅图来展示一下我考虑实现这个函数的思路吧。

2.2 根据Promise/A+规范实现Promise

人家有相关标准,我们就要遵守,毕竟遵纪守法才是好公民,现在只能硬着头皮把这个标准过一遍。

下面就是基于Promise/A+规范实现的代码,已经经过promises-aplus-tests库进行了验证。
const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';/** * Promise构造函数 * excutor: 内部同步执行的函数 */class Promise {    constructor(excutor) {        const self = this;        self.status = PENDING;        self.onFulfilled = [];// 成功的回调        self.onRejected = [];// 失败的回调        // 异步处理成功调用的函数        // PromiseA+ 2.1 状态只能由Pending转为fulfilled或rejected;fulfilled状态必须有一个value值;rejected状态必须有一个reason值。        function resolve(value) {            if (self.status === PENDING) {                self.status = FULFILLED;                self.value = value;                 // PromiseA+ 2.2.6.1 相同promise的then可以被调用多次,当promise变为fulfilled状态,全部的onFulfilled回调按照原始调用then的顺序执行                self.onFulfilled.forEach(fn => fn());            }        }        function reject(reason) {            if (self.status === PENDING) {                self.status = REJECTED;                self.reason = reason;                // PromiseA+ 2.2.6.2 相同promise的then可以被调用多次,当promise变为rejected状态,全部的onRejected回调按照原始调用then的顺序执行                self.onRejected.forEach(fn => fn());            }        }        try {            excutor(resolve, reject);        } catch (e) {            reject(e);        }    }    then(onFulfilled, onRejected) {        // PromiseA+ 2.2.1 onFulfilled和onRejected是可选参数        // PromiseA+ 2.2.5 onFulfilled和onRejected必须被作为函数调用        // PromiseA+ 2.2.7.3 如果onFulfilled不是函数且promise1状态是fulfilled,则promise2有相同的值且也是fulfilled状态        // PromiseA+ 2.2.7.4 如果onRejected不是函数且promise1状态是rejected,则promise2有相同的值且也是rejected状态        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };        const self = this;        const promise = new Promise((resolve, reject) => {            const handle = (callback, data) => {                // PromiseA+ 2.2.4 onFulfilled或者onRejected需要在自己的执行上下文栈里被调用,所以此处用setTimeout                setTimeout(() => {                    try {                         // PromiseA+ 2.2.2 如果onFulfilled是函数,则在fulfilled状态之后调用,第一个参数为value                        // PromiseA+ 2.2.3 如果onRejected是函数,则在rejected状态之后调用,第一个参数为reason                        const x = callback(data);                        // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一个x值,运行这[[Resolve]](promise2, x)                        resolvePromise(promise, x, resolve, reject);                    } catch (e) {                        // PromiseA+ 2.2.7.2 onFulfilled或onRejected抛出一个异常e,promise2必须以e的理由失败                        reject(e);                    }                })            }            if (self.status === PENDING) {                self.onFulfilled.push(() => {                    handle(onFulfilled, self.value);                });                self.onRejected.push(() => {                    handle(onRejected, self.reason);                })            } else if (self.status === FULFILLED) {                setTimeout(() => {                    handle(onFulfilled, self.value);                })            } else if (self.status === REJECTED) {                setTimeout(() => {                    handle(onRejected, self.reason);                })            }        })        return promise;    }}function resolvePromise(promise, x, resolve, reject) {    // PromiseA+ 2.3.1 如果promise和x引用同一对象,会以TypeError错误reject promise    if (promise === x) {        reject(new TypeError('Chaining Cycle'));    }    if (x && typeof x === 'object' || typeof x === 'function') {        // PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。         let used;        try {            // PromiseA+ 2.3.3.1 let then be x.then            // PromiseA+ 2.3.2 调用then方法已经包含了该条(该条是x是promise的处理)。            let then = x.then;            if (typeof then === 'function') {                // PromiseA+ 2.3.3.3如果then是一个函数,用x作为this调用它。第一个参数是resolvePromise,第二个参数是rejectPromise                // PromiseA+ 2.3.3.3.1 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)                // PromiseA+ 2.3.3.3.2 如果rejectPromise用一个原因r调用,用r拒绝promise。                then.call(x, (y) => {                    if (used) return;                    used = true;                    resolvePromise(promise, y, resolve, reject)                }, (r) => {                    if (used) return;                    used = true;                    reject(r);                })            } else {                // PromiseA+ 如果then不是一个函数,变为fulfilled状态并传值为x                if (used) return;                used = true;                resolve(x);            }        } catch (e) {            // PromiseA+ 2.3.3.2 如果检索属性x.then抛出异常e,则以e为原因拒绝promise            // PromiseA+ 2.3.3.4 如果调用then抛出异常,但是resolvePromise或rejectPromise已经执行,则忽略它            if (used) return;            used = true;            reject(e);        }    } else {        // PromiseA+ 2.3.4 如果x不是一个对象或函数,状态变为fulfilled并传值x        resolve(x);    }}

2.2 其他方法

按照Promise/A+规范实现了Promise的核心内容,但是其只实现了Promise.prototype.then()方法,那其它方法呢?下面我们就唠一唠其它方法,包括静态方法(Promise.resolve()、Promise.reject()、Promise.all()、Promise.race())和实例方法(Promise.prototype.catch()、Promise.prototype.finally())。

2.2.1 Promise.resolve()

class Promise {    // ...    // 将现有对象转为 Promise 对象    static resolve(value) {        // 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。        if (value instanceof Promise) return value;        // 参数是一个thenable对象(具有then方法的对象),Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。        if (typeof value === 'object' || typeof value === 'function') {            try {                let then = value.then;                if (typeof then === 'function') {                    return new Promise(then.bind(value));                }            } catch (e) {                return new Promise((resolve, reject) => {                    reject(e);                })            }        }        // 参数不是具有then方法的对象,或根本就不是对象,Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。        return new Promise((resolve, reject) => {            resolve(value);        })    }}

2.2.2 Promise.reject()

class Promise {    // ...    // 返回一个新的 Promise 实例,该实例的状态为rejected。    static reject(reason) {        return new Promise((resolve, reject) => {            reject(reason);        })    }}

2.2.3 Promise.all()

class Promise {    // ...    // 用于将多个 Promise 实例,包装成一个新的 Promise 实例。只有所有状态都变为fulfilled,p的状态才会是fulfilled    static all(promises) {        const values = [];        let resolvedCount = 0;        return new Promise((resolve, reject) => {            promises.forEach((p, index) => {                Promise.resolve(p).then(value => {                    resolvedCount++;                    values[index] = value;                    if (resolvedCount === promises.length) {                        resolve(values);                    }                }, reason => {                    reject(reason);                })            })        })    }}

2.2.4 Promise.race()

class Promise {    // ...     // 只要有一个实例率先改变状态,状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给回调函数。    static race(promises) {        return new Promise((resolve, reject) => {            promises.forEach((p, index) => {                Promise.resolve(p).then(value => {                    resolve(value);                }, reason => {                    reject(reason);                })            })        })    }}

2.2.5 Promise.catch()

class Promise {    // ...    // 是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。    catch(onRejected) {        return this.then(undefined, onRejected);    }}

2.2.6 Promise.finally()

class Promise {    // ...    // 用于指定不管 Promise 对象最后状态如何,都会执行的操作。    finally(callback) {        return this.then(            value => Promise.resolve(callback()).then(() => value),            reason => Promise.resolve(callback()).then(() => { throw reason })        )    }}

三、Async原理实现

在开发过程中常用的另一种异步方案莫过于Async,通过async函数的引入使得异步操作变得更加方便。实质上,async是Generator的语法糖,最大的亮点是async内置执行器,调用后即可自动执行,不像Generator需要调用next()方法才能执行。

这是Async的实现原理,即将Generator函数作为参数放入run函数中,最终实现自动执行并返回Promise对象。
function run(genF) {    // 返回值是Promise    return new Promise((resolve, reject) => {        const gen = genF();        function step(nextF) {            let next;            try {                // 执行该函数,获取一个有着value和done两个属性的对象                next = nextF();            } catch (e) {                // 出现异常则将该Promise变为rejected状态                reject(e);            }            // 判断是否到达末尾,Generator函数到达末尾则将该Promise变为fulfilled状态            if (next.done) {                return resolve(next.value);            }            // 没到达末尾,则利用Promise封装该value,直到执行完毕,反复调用step函数,实现自动执行            Promise.resolve(next.value).then((v) => {                step(() => gen.next(v))            }, (e) => {                step(() => gen.throw(e))            })        }        step(() => gen.next(undefined));    })}

四、发布/订阅实现

更加详细内容可以参考《图解23种设计模式》

发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。订阅者(观察者)把自己想要订阅的事件注册到调度中心,当该事件触发的时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。

// 发布订阅(TypeScript版)interface Publish {    registerObserver(eventType : string, subscribe : Subscribe) : void;    remove(eventType : string, subscribe ?: Subscribe) : void;    notifyObservers(eventType : string) : void;}interface SubscribesObject{    [key : string] : Array<Subscribe>}class ConcretePublish implements Publish {    private subscribes : SubscribesObject;    constructor() {        this.subscribes = {};    }    registerObserver(eventType : string, subscribe : Subscribe) : void {        if (!this.subscribes[eventType]) {            this.subscribes[eventType] = [];        }        this.subscribes[eventType].push(subscribe);    }    remove(eventType : string, subscribe ?: Subscribe) : void {        const subscribeArray = this.subscribes[eventType];        if (subscribeArray) {            if (!subscribe) {                delete this.subscribes[eventType];            } else {                for (let i = 0; i < subscribeArray.length; i++) {                    if (subscribe === subscribeArray[i]) {                        subscribeArray.splice(i, 1);                    }                }            }        }    }    notifyObservers(eventType : string, ...args : any[]) : void {        const subscribes = this.subscribes[eventType];        if (subscribes) {            subscribes.forEach(subscribe => subscribe.update(...args))        }    }}interface Subscribe {    update(...value : any[]) : void;}class ConcreteSubscribe1 implements Subscribe {    public update(...value : any[]) : void {        console.log('已经执行更新操作1,值为', ...value);    }}class ConcreteSubscribe2 implements Subscribe {    public update(...value : any[]) : void {        console.log('已经执行更新操作2,值为', ...value);    }}function main() {    const publish = new ConcretePublish();    const subscribe1 = new ConcreteSubscribe1();    const subscribe2 = new ConcreteSubscribe2();    publish.registerObserver('1', subscribe1);    publish.registerObserver('2', subscribe2);    publish.notifyObservers('2', '22222');}main();
相关章节
图解JavaScript——代码实现【1】
图解JavaScript————基础篇
图解JavaScript————进阶篇
图解23种设计模式(TypeScript版)

参考链接
Prmose/A+
Promise源码实现
ES6入门教程

欢迎大家关注公众号(回复“异步”获取本节的思维导图,回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频)