图解 JavaScript——代码实现(六种异步方案, 重点是 Promise、Async、发布 / 订阅原理实现,真香)

2023-10-27

图解 JavaScript——代码实现(六种异步方案, 重点是 Promise、Async、发布 / 订阅原理实现,真香)

图解JavaScript——代码实现(六种异步方案,重点是Promise、Async、发布/订阅原理实现,真香)

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

一、六种异步方案

img

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())进行实现。

img

2.1 思考一下

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

img

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

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

img

下面就是基于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())。

img

2.2.1 Promise.resolve()

img

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()

img

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

2.2.3 Promise.all()

img

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()

img

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()

img

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

2.2.6 Promise.finally()

img

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()方法才能执行。

img

这是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种设计模式》

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

img

// 发布订阅(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();

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

图解 JavaScript——代码实现(六种异步方案, 重点是 Promise、Async、发布 / 订阅原理实现,真香) 的相关文章

  • Navicat Premium下sql导入中文乱码解决方案

    最近帮客户导数据比较多 用阿里云感觉不方便 Navicat Premium还是好用 但是数据稻城excel就会乱码 解决方案 1 看图说话 右键选中 2 三条SQL完成 mysql gt show databases Database in

随机推荐

  • 「Python 面试」第六次面试

    1 说一说 Redis 是什么 Redis 是一种 Key Value 的内存型 非关系型数据库 属于 NoSQL 的一种 Redis 的读写速度特别快 特别适合读写频繁的场景 Redis 支持主从复制 支持数据持久化 2 知道 Redis
  • MMD相关制作

    1 导入可转换mmd和vmd 动作数据 的插件 导入后unity中为 2 导入模型和vmd和音频 3 在unity中点击模型右边弹出的协议全打勾并应用 4 其中的mmd动作数据转换成功 点击人物把rig下第一个选择Huamoid点击应用 再
  • Vue中nextTick使用

    1 语法 this nextTick 回调函数 2 作用 在下一次Dom更新结束后执行其指定的回调函数 3 使用场景 当数据改变后 要基于更新后的新Dom进行某些操作时 要在nextTick所指定的回调函数中执行
  • 查询oracle所用用户,查询所有用户(oracle查询所有用户)

    查询所有用户 oracle查询所有用户 2020 07 24 11 10 05 共10个回答 1 查询oracle中所有用户信息select fromdba users 2 只查询用户和密码selectusername passwordfr
  • 数据结构---桶排序

    桶排序 第一步 第二步 第三步 第四步 JAVA实现 时间复杂度 空间复杂度总结 每一个桶 bucket 代表一个区间范围 里面可以承载一个或多个元素 第一步 就是创建这些桶 并确定每一个桶的区间范围 我们这里创建的桶数量等于原始数列的元素
  • python Matplotlib库基础

    目录 Matplotlib 数据可视化入门 Pyplot 绘图 自定义配置文件 rcParams 创建绘图窗口 绘制子图 绘制饼图 绘制折线图 绘制条形图 绘制散点图 绘制热点图 绘制箱型图 绘制分类图背景 显示绘图窗口 DataFrame
  • FATFS文件系统f_mkfs函数详解

    1 f mkfs参数 参数path 要挂载 卸载的逻辑驱动器号 使用设备根路径表示 参数opt 系统的格式 如图所示 若需要格式化为FAT32文件系统 则选择FM FAT32即可 若需要格式化为exFAT文件系统 则应该开将宏定义 defi
  • kNN做回归任务

    kNN回归 kNN常用作分类任务 但是也可以做回归任务 做法 使用kNN计算某个数据点的预测值时 模型会从训练数据集中选择离该数据点最近的k个数据点 并且把他们的y值取均值 把该均值作为新数据点的预测值 代码 此次代码演示使用数据库中的鸢尾
  • AI业务强劲增长,百度迎来了“推卒过河”的纵横时刻

    文 螳螂观察 作者 陈淼 科技创新面临的处境与机遇大多与中国象棋中的 卒 相似 单次只能走一步 不像其他棋子一次能走多步 然而一旦 推卒过河 卒 就可纵可横 能发挥出极大的作用 因此 也就有了 过河走卒胜似车 的谚语 这种境遇像极了今天在人
  • linux下执行shell脚本报“ $'\r':command not found…”错误

    1 现象 在linux下执行脚本有时会出现错误如下 r command not found 2 原因分析 脚本本身却没有错误 是由于脚本在windows下打开过编辑过 因为在windows下的换行是回车 换行 r n 而在linux下的换行
  • BeautifulSoup解析通过js生成内容的本地html文件

    问题 当本地html文件中的元素都是由js生成时 我们无法通过beautifulsoup进行解析 思路 1 通过webdriver的无头浏览器 不在桌面打开浏览器的情况下 通过浏览器引擎加载html文件 2 获取浏览器的页面资源 3 将资源
  • 命名Java变量

    Java变量的命名规则 在面向对象编程中 对于包 类 方法和常量的命名都是有规则的 例如 英文大小写的区分 1 包的命名 包的命名都是由小写字母组成 为了保障每个Java包命名的唯一性 应在自己定义的包的名称前加上唯一的前缀 例如 edu
  • java solr功能代码

    package com wlsq search center util import org apache solr client solrj SolrQuery import org apache solr client solrj im
  • 《数据结构》02-线性结构3 Reversing Linked List

    题目 Given a constant K and a singly linked list L you are supposed to reverse the links of every K elements on L For exam
  • dns备用服务器信息,dns服务器地址(dns首选和备用填多少)

    dns服务器地址 DNS是计算机域名体系 DomainNameSystem或DomainNameService 的缩写 它是由解析器以及域名服务器组成的 域名服务器是指保存有该网络中所有主机的域名和对应IP地址 并具有将域名转换为IP地址功
  • CocosCreator Java传参数到JS

    最近正在接GooglePlay内购 在传参数回CocosCreator的环境的时候 没有调用到JS的方法 其中错误的写法是 app runOnGLThread new Runnable Override public void run Co
  • IPv6基础介绍

    IPv4理论上仅仅能够提供的地址数量是43亿 但是由于地址分配机制等原因 实际可使用的数量还远远达不到43亿 因特网的迅猛发展令人始料未及 同时也带来了地址短缺的问题 针对这一问题 曾先后出现过几种解决方案 比如CIDR和NAT 但是CID
  • npm ERR! Log files were not written due to an error writing to the directory: D:\Program Files (x86)

    配置前端环境报错 之前配置过vue环境 现在再次使用时报错 解决 第一步 删除C Users 用户 下的 npmrc文件 我的是已经删除过了 第二步 在dos命令下输入 npm cache clean force 第三步 然后再输入命令 n
  • QT中QString字符串的大小写转换函数

    str toLower 转换为小写 str toUpper 转换为大写
  • 图解 JavaScript——代码实现(六种异步方案, 重点是 Promise、Async、发布 / 订阅原理实现,真香)

    图解 JavaScript 代码实现 六种异步方案 重点是 Promise Async 发布 订阅原理实现 真香 本节主要阐述六种异步方案 回调函数 事件监听 发布 订阅 Promise Generator和Async 其中重点是发布 订阅