1. <output id="xofhe"></output>

      <menuitem id="xofhe"></menuitem>
      当前位置: 首页 / 技术分享 / 正文
      手封MyPromise

      2022-12-02

         mypromise

        引子

        面试时如果被问到“如果没有Promise,能不能自己实现一个”,就是在深度考察对异步的理解了;

        如果你的回答是“能,我封过”,则必然令人刮目相看~

        事实上,作为ES6新增的API,即使没有,用户也完全可以基于想要的效果自己去实现一个;

        这里给到大家我个人的实现:MyPromise;

        源码地址

        实现思路

        将then/catch/finally中入参的回调函数按顺序收集到一个队列中;

        亲自实现一下resolve和reject方法;

        一旦实例resolve就将队列头部的catch回调都弹出,一旦实例reject就将队列头部的then回调都弹出,然后拿取队列头部的回调函数实行起来;

        每次回调的执行结果即返回值需要继续resolve或reject一下,然后继续递归执行上述过程,直到回调队列为空;

        接收任务执行器

        当我们new出一个MyPromise,并内置一个任务执行器函数的时候,需要在MyPromise的构造器中接收任务执行器函数,并同步地、立即地将其执行起来;

        要特别注意:任务执行器函数是同步地跑在主线程的,而不属于异步回调;

        test.js

        new MyPromise(

        /* 任务执行器:2秒后随机履约或毁约 */

        (resolve, reject) => {

        setTimeout(() => {

        Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));

        }, 2000);

        }

        )

        MyPromise.js

        class MyPromise {

        constructor(executor) {

        /* 接收任务执行器并立即调用(同步调用) */

        this.executor = executor;

        this.executor();

        }

        }

        定义resolve与reject

        任务执行器函数会在稍后履约(resolve)或毁约(reject)

        因此要告诉MyPromise履约或毁约时具体做什么

        这里我们先做一下简单的打印

        MyPromise.js

        class MyPromise {

        constructor(executor) {

        /* 接收任务执行器并立即调用(同步调用) */

        /* 绑定执行器函数中的resolve与reject */

        this.executor = executor.bind(

        this, //当前promise实例

        

        //当前promise实例执行resolve时,this不变

        MyPromise.doResolve.bind(this),

        

        //当前promise实例执行reject时,this不变

        MyPromise.doReject.bind(this)

        );

        this.executor();

        }

        

        /* promise实例执行resolve */

        static doResolve(data) {

        console.log("doResolve", data);

        }

        

        /* promise实例执行reject */

        static doReject(err) {

        console.log("doReject", err);

        }

        }

        

        module.exports = MyPromise;

        明确回调期望

        明确一下异步任务履约/毁约后我们希望做什么

        我们希望一旦异步任务resolve就执行then中的回调

        我们希望一旦异步任务reject就执行catch中的回调

        我们希望异步任务无论成功还是失败,最终都执行finally中的回调

        期望如下:test.js

        const MyPromise = require("./MyPromiseX")

        

        new MyPromise(

        /* 任务执行器:2秒后随机履约或毁约 */

        (resolve, reject) => {

        setTimeout(() => {

        Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));

        }, 2000);

        }

        )

        .then(

        data => {

        console.log("then1:data=",data)

        return "data from then1"

        // throw new Error("err from then1")

        }

        )

        .catch(

        err => {

        console.log("catch1:err=",err)

        return "data from catch1"

        }

        )

        .finally(

        ()=>console.log("finally:game over!")

        )

        收集回调队列

        每一个then中有一个成功回调

        每一个catch中有一个失败回调

        finally中有一个最终回调

        我们先按顺序将这些回调收集在一个队列中

        代码如下:MyPromise.js

        class MyPromise {

        constructor(executor) {

        

        // 回调队列

        this.callbacks = [];

        

        /* 接收任务执行器并立即调用(同步调用) */

        // 绑定执行器函数中的resolve与reject

        this.executor = executor.bind(

        this, //当前promise实例

        

        //当前promise实例执行resolve时,this不变

        MyPromise.doResolve.bind(this),

        

        //当前promise实例执行reject时,this不变

        MyPromise.doReject.bind(this)

        );

        

        // 立即调用任务执行器

        this.executor();

        }

        

        /* promise实例执行resolve */

        static doResolve(data) {

        console.log("doResolve", data);

        }

        

        /* promise实例执行reject */

        static doReject(err) {

        console.log("doReject", err);

        }

        

        /* 收集成功回调到队列 */

        then(onData) {

        // 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废

        this.callbacks.push({ type: "then", callback: onData });

        return this;

        }

        

        /* 收集失败回调到队列 */

        catch(onErr) {

        // 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废

        this.callbacks.push({ type: "catch", callback: onErr });

        return this;

        }

        

        /* 收集终点回调到队列(此处假设只有一个终点回调) */

        finally(onFinish) {

        this.callbacks.push({ type: "finally", callback: onFinish });

        return this;

        }

        }

        

        module.exports = MyPromise;

        此时在测试脚本test.js的末尾打印一下回调队列的话,你会看到如下输出:

        [

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'catch', callback: [Function (anonymous)] },

        { type: 'finally', callback: [Function (anonymous)] }

        ]

        如果来上一堆乱序的then/catch/finally的话,则你会看到如下输出:

        [

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'catch', callback: [Function (anonymous)] },

        { type: 'catch', callback: [Function (anonymous)] },

        { type: 'then', callback: [Function (anonymous)] },

        { type: 'finally', callback: [Function (anonymous)] }

        ]

        连环作案(履约或毁约)

        让我们在每次成功或失败后,都随机地继续履约或毁约起来!

        test.js

        const MyPromise = require("./MyPromiseX")

        

        const p = new MyPromise(

        /* 任务执行器:2秒后随机履约或毁约 */

        (resolve, reject) => {

        setTimeout(() => {

        Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));

        }, 2000);

        }

        )

        .then(

        data => {

        console.log("then1:data=",data)

        

        /* 继续履约或毁约! */

        return "data from then1"

        // throw new Error("err from then1")

        }

        )

        .then(

        data => {

        console.log("then2:data=",data)

        

        /* 继续履约或毁约! */

        return MyPromise.resolve("data from then2")

        // return MyPromise.reject("err from then2")

        }

        )

        .then(

        data => {

        console.log("then3:data=",data)

        

        /* 继续履约或毁约! */

        return new MyPromise(

        (resolve,reject) => setTimeout(() => {

        resolve("data from then3")

        // reject("err from then3")

        }, 2000)

        )

        }

        )

        .then(

        /* 这里本质继续履约了一个undefined */

        data => console.log("then4:data=",data)

        )

        .catch(

        err => {

        console.log("catch1:err=",err)

        

        /* 继续履约 */

        return "data from catch1"

        }

        )

        .catch(

        /* 这里本质继续履约了一个undefined */

        err => console.log("catch2:err=",err)

        )

        .then(

        /* 这里本质继续履约了一个undefined */

        data => console.log("then5:data=",data)

        )

        .finally(

        /* 这里本质继续履约了一个undefined */

        ()=>console.log("finally:game over!")

        )

        

        // 打印一下MyPromise对象的回调队列

        console.log("p.callbacks",p.callbacks);

        所谓MyPromise.resolve(data)或MyPromise.reject(err)无非也就是newPromise(executor)的一个语法糖而已,实现如下:

        MyPromise.js片段

        /* 语法糖:创建一个立即resolve的Promise对象 */

        static resolve(data) {

        return new MyPromise((resolve) => resolve(data));

        }

        

        /* 语法糖:创建一个立即reject的Promise对象 */

        static reject(err) {

        return new MyPromise((resolve, reject) => reject(err));

        }

        准备执行连环回调

        我们无非想要如下效果:

        只要前面的环节履约了:在回调队列中找出下一个type为then的回调执行起来;

        只要前面的环节毁约了:在回调队列中找出下一个type为catch的回调执行起来;

        找下一个then回调前,所有位于它前面的catch都驱逐先;

        找下一个catch回调前,所有位于它前面的then都驱逐先;

        我们来撸码实现之!

        定义MyPromise状态

        class MyPromise {

        /* Promise状态定义 */

        static STATUS_PENDING = 0; // 挂起态

        static STATUS_FULFILLED = 1; // 履约态

        static STATUS_REJECTED = 2; // 毁约态

        ...

        }

        定义回调入参

        constructor(executor) {

        /* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */

        this.callbacks = [];

        

        // 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg

        this.cbArg = null;

        ...

        }

        定义驱逐器

        成功时:从回调队列头部把所有的catch驱逐

        失败时:从回调队列头部把所有的then驱逐

        /*

        成功时:从回调队列头部把所有的catch驱逐

        失败时:从回调队列头部把所有的then驱逐

        */

        shiftCallbacksWithType(type) {

        /* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */

        while (this.callbacks.length && this.callbacks[0].type === type) {

        // 驱逐一个回调函数

        this.callbacks.shift();

        }

        }

        执行连环回调

        当前任履约时

        状态设置为履约态

        将回调入参设置为需要履约的数据

        找下一个then里的回调执行起来

        /* promise实例执行resolve */

        static doResolve(data) {

        /* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */

        this.status = MyPromise.STATUS_FULFILLED;

        

        // 回调入参为数据

        this.cbArg = data;

        

        // 拉起下一次then回调

        setTimeout(() => {

        this.next();

        });

        }

        当前任毁约时

        状态设置为毁约态

        将回调入参设置为毁约的原因

        找下一个catch里的回调执行起来

        /* promise实例执行reject */

        static doReject(err) {

        /* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */

        this.status = MyPromise.STATUS_REJECTED;

        

        // 回调入参为错误

        this.cbArg = err;

        

        // 拉起下一次catch回调

        setTimeout(() => {

        this.next();

        });

        }

        执行下一次回调

        整个MyPromise最核心的逻辑来了

        只要当前MyPromise为履约态,就驱逐队列头部所有的catch回调先

        只要当前MyPromise为毁约态,就驱逐队列头部所有的then回调先

        拿取队列头部的那个回调执行起来

        next() {

        /* 确定该回调哪一个callback */

        if (this.status === MyPromise.STATUS_FULFILLED) {

        // 履约时:弹光队列头部的catch回调

        this.shiftCallbacksWithType("catch");

        }

        

        if (this.status === MyPromise.STATUS_REJECTED) {

        // 毁约时:弹光队列头部的then回调

        this.shiftCallbacksWithType("then");

        }

        

        /* 如果回调队列已空,则直接结束程序 */

        if (!this.callbacks.length) {

        console.log("回调队列已空");

        return;

        }

        

        /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

        let { callback } = this.callbacks.shift();

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        }

        当回调返回value

        test.js片段

        .then(

        data => {

        console.log("then1:data=",data)

        

        /* 继续履约或毁约! */

        return "data from then1"

        // throw new Error("err from then1")

        }

        )

        当回调返回一个新的(非MyPromise对象)的普通value时,我们继续向后resolve它

        程序会继续doResolve,doResolve内部会继续拉起下一次next

        下一次next找出的回调还有新的返回值

        递归了解一下!归了解一下!了解一下!解一下!一下!下!

        next() {

        ...

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        

        /* 如果回调函数返回一个value 继续向下resolve(value) */

        if (!(value instanceof MyPromise)) {

        MyPromise.doResolve.call(this, value);

        }

        ...

        }

        当回调抛出错误时

        test.js片段

        .then(

        data => {

        console.log("then1:data=",data)

        

        /* 继续履约或毁约! */

        // return "data from then1"

        throw new Error("err from then1")

        }

        )

        try-catch起来,把错误信息reject一下

        程序会继续doReject,doReject内部会继续拉起下一次next

        下一次next找出的回调还有新的返回值

        递归了解一下!归了解一下!了解一下!解一下!一下!下!

        next() {

        ...

        /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

        let { callback } = this.callbacks.shift();

        

        /* 防止回调函数中throw错误 */

        try {

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        

        /* 如果回调函数返回一个value 继续向下resolve(value) */

        if (!(value instanceof MyPromise)) {

        MyPromise.doResolve.call(this, value);

        }else {

        ...

        }

        } catch (err) {

        // 回调函数抛出错误时相当于reject(err)

        MyPromise.doReject.call(this, err);

        }

        }

        当回调返回一个新的MP对象时

        .then(

        data => {

        console.log("then2:data=",data)

        

        /* 继续履约或毁约! */

        return MyPromise.resolve("data from then2")

        // return MyPromise.reject("err from then2")

        }

        )

        .then(

        data => {

        console.log("then3:data=",data)

        

        /* 继续履约或毁约! */

        return new MyPromise(

        (resolve,reject) => setTimeout(() => {

        resolve("data from then3")

        // reject("err from then3")

        }, 2000)

        )

        }

        )

        首先,MP的构造器一定会被调用!

        构造器里有this.executor(),this.executor()里有resolve(data)或reject(err),也就是doResolve(data)或doReject(err)

        不断递归next()找下一次回调的过程会自发地跑起

        这个该死的MP对象可能自带一个回调队列

        我们把当前MP对象剩余的回调队列过户给它即可

        next() {

        ...

        /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

        let { callback } = this.callbacks.shift();

        

        /* 防止回调函数中throw错误 */

        try {

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        

        /* 如果回调函数返回一个value 继续向下resolve(value) */

        if (!(value instanceof MyPromise)) {

        ...

        }

        else {

        // 如果回调函数返回一个Promise对象

        // 将后续所有callback过户给新的Promise对象

        value.callbacks = value.callbacks.concat(this.callbacks);

        }

        } catch (err) {...}

        }

        完整的next函数

        递归inside!

        /*

        从回调队列里拉取下一个适当的callback并回调之

        这是MyPromise的核心代码:递归inside!

        */

        next() {

        /* 确定该回调哪一个callback */

        if (this.status === MyPromise.STATUS_FULFILLED) {

        // 履约时:弹光队列头部的catch回调

        this.shiftCallbacksWithType("catch");

        }

        

        if (this.status === MyPromise.STATUS_REJECTED) {

        // 毁约时:弹光队列头部的then回调

        this.shiftCallbacksWithType("then");

        }

        

        /* 如果回调队列已空,则直接结束程序 */

        if (!this.callbacks.length) {

        console.log("回调队列已空");

        return;

        }

        

        /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

        let { callback } = this.callbacks.shift();

        

        /* 防止回调函数中throw错误 */

        try {

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        

        /* 如果回调函数返回一个value 继续向下resolve(value) */

        if (!(value instanceof MyPromise)) {

        MyPromise.doResolve.call(this, value);

        }

        else {

        // 如果回调函数返回一个Promise对象

        // 将后续所有callback过户给新的Promise对象

        value.callbacks = value.callbacks.concat(this.callbacks);

        }

        } catch (err) {

        // 回调函数抛出错误时相当于reject(err)

        MyPromise.doReject.call(this, err);

        }

        }

        完整代码

        MyPromise.js

        class MyPromise {

        /* Promise状态定义 */

        static STATUS_PENDING = 0; // 挂起态

        static STATUS_FULFILLED = 1; // 履约态

        static STATUS_REJECTED = 2; // 毁约态

        

        constructor(executor) {

        /* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */

        this.callbacks = [];

        

        // 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg

        this.cbArg = null;

        

        /* 绑定执行器函数中的resolve与reject */

        this.executor = executor.bind(

        this, //当前promise实例

        

        //当前promise实例执行resolve时,this不变

        MyPromise.doResolve.bind(this),

        

        //当前promise实例执行reject时,this不变

        MyPromise.doReject.bind(this)

        );

        

        // 执行任务前先将Promise状态设置为pending

        this.status = MyPromise.STATUS_PENDING;

        

        // 执行任务(任务中会resolve/doResolve或reject/doReject)

        this.executor();

        }

        

        /* promise实例执行resolve */

        static doResolve(data) {

        /* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */

        this.status = MyPromise.STATUS_FULFILLED;

        

        // 回调入参为数据

        this.cbArg = data;

        

        // 拉起下一次then回调

        setTimeout(() => {

        this.next();

        });

        }

        

        /* promise实例执行reject */

        static doReject(err) {

        /* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */

        this.status = MyPromise.STATUS_REJECTED;

        

        // 回调入参为错误

        this.cbArg = err;

        

        // 拉起下一次catch回调

        setTimeout(() => {

        this.next();

        });

        }

        

        /*

        成功时:从回调队列头部把所有的catch驱逐

        失败时:从回调队列头部把所有的then驱逐

        */

        shiftCallbacksWithType(type) {

        /* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */

        while (this.callbacks.length && this.callbacks[0].type === type) {

        // 驱逐一个回调函数

        this.callbacks.shift();

        }

        }

        

        /*

        从回调队列里拉取下一个适当的callback并回调之

        这是MyPromise的核心代码:递归inside!

        */

        next() {

        /* 确定该回调哪一个callback */

        if (this.status === MyPromise.STATUS_FULFILLED) {

        // 履约时:弹光队列头部的catch回调

        this.shiftCallbacksWithType("catch");

        }

        

        if (this.status === MyPromise.STATUS_REJECTED) {

        // 毁约时:弹光队列头部的then回调

        this.shiftCallbacksWithType("then");

        }

        

        /* 如果回调队列已空,则直接结束程序 */

        if (!this.callbacks.length) {

        console.log("回调队列已空");

        return;

        }

        

        /* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */

        let { callback } = this.callbacks.shift();

        

        /* 防止回调函数中throw错误 */

        try {

        // 执行回调并拿到其结果(value或promise对象)

        let value = callback(this.cbArg);

        

        /* 如果回调函数返回一个value 继续向下resolve(value) */

        if (!(value instanceof MyPromise)) {

        MyPromise.doResolve.call(this, value);

        }

        else {

        // 如果回调函数返回一个Promise对象

        // 将后续所有callback过户给新的Promise对象

        value.callbacks = value.callbacks.concat(this.callbacks);

        }

        } catch (err) {

        // 回调函数抛出错误时相当于reject(err)

        MyPromise.doReject.call(this, err);

        }

        }

        

        /* 语法糖:创建一个立即resolve的Promise对象 */

        static resolve(data) {

        return new MyPromise((resolve) => resolve(data));

        }

        

        /* 语法糖:创建一个立即reject的Promise对象 */

        static reject(err) {

        return new MyPromise((resolve, reject) => reject(err));

        }

        

        /* 收集成功回调到队列 */

        then(onData) {

        // 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废

        this.callbacks.push({ type: "then", callback: onData });

        return this;

        }

        

        /* 收集失败回调到队列 */

        catch(onErr) {

        // 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废

        this.callbacks.push({ type: "catch", callback: onErr });

        return this;

        }

        

        /* 收集终点回调到队列(此处假设只有一个终点回调) */

        finally(onFinish) {

        this.callbacks.push({ type: "finally", callback: onFinish });

        return this;

        }

        }

        

        module.exports = MyPromise;

        测试代码test.js

        const MyPromise = require("./MyPromise3")

        

        const p = new MyPromise(

        /* 任务执行器:2秒后随机履约或毁约 */

        (resolve, reject) => {

        setTimeout(() => {

        Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));

        }, 2000);

        }

        )

        .then(

        data => {

        console.log("then1:data=",data)

        

        /* 继续履约或毁约! */

        return "data from then1"

        // throw new Error("err from then1")

        }

        )

        .then(

        data => {

        console.log("then2:data=",data)

        

        /* 继续履约或毁约! */

        return MyPromise.resolve("data from then2")

        // return MyPromise.reject("err from then2")

        }

        )

        .then(

        data => {

        console.log("then3:data=",data)

        

        /* 继续履约或毁约! */

        return new MyPromise(

        (resolve,reject) => setTimeout(() => {

        resolve("data from then3")

        // reject("err from then3")

        }, 2000)

        )

        }

        )

        .then(

        /* 这里本质继续履约了一个undefined */

        data => console.log("then4:data=",data)

        )

        .catch(

        err => {

        console.log("catch1:err=",err)

        

        /* 继续履约 */

        return "data from catch1"

        }

        )

        .catch(

        /* 这里本质继续履约了一个undefined */

        err => console.log("catch2:err=",err)

        )

        .then(

        /* 这里本质继续履约了一个undefined */

        data => console.log("then5:data=",data)

        )

        .finally(

        /* 这里本质继续履约了一个undefined */

        ()=>console.log("finally:game over!")

        )

        

        // 打印一下MyPromise对象的回调队列

        console.log("p.callbacks",p.callbacks);

        测试效果

        https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c47910678fd41acba1eed2ea02bbabb~tplv-k3u1fbpfcp-watermark.image?

      好程序员公众号

      • · 剖析行业发展趋势
      • · 汇聚企业项目源码

      好程序员开班动态

      More+
      • HTML5大前端 <高端班>

        开班时间:2021-04-12(深圳)

        开班盛况

        开班时间:2021-05-17(北京)

        开班盛况
      • 大数据+人工智能 <高端班>

        开班时间:2021-03-22(杭州)

        开班盛况

        开班时间:2021-04-26(北京)

        开班盛况
      • JavaEE分布式开发 <高端班>

        开班时间:2021-05-10(北京)

        开班盛况

        开班时间:2021-02-22(北京)

        开班盛况
      • Python人工智能+数据分析 <高端班>

        开班时间:2021-07-12(北京)

        预约报名

        开班时间:2020-09-21(上海)

        开班盛况
      • 云计算开发 <高端班>

        开班时间:2021-07-12(北京)

        预约报名

        开班时间:2019-07-22(北京)

        开班盛况
      在线咨询
      试听
      入学教程
      立即报名

      Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号

      成熟妓女BBw
        1. <output id="xofhe"></output>

          <menuitem id="xofhe"></menuitem>