build-your-own-promise 构建自己的promise

来源:互联网 发布:2017招聘软件排行 编辑:程序博客网 时间:2024/06/08 13:40

promise

随着前端技术的发展,promise也越来越流行,在前端开发中常见的 promise主要有:

  • jQuery的$.defer
  • angular的$q
  • node的第三方模块q

用的多了,也就想了解一下原理,同时也想重复造个轮子,加深自己的印象。上面提到的三种promise实现方案并不一样,本文主要参考node的q模块(https://github.com/kriskowal/q/tree/v1/design)

对应的github地址为:https://github.com/chenkehxx/build-your-own-promise

q0.js

熟悉promise的人都了解,常用的defer主要有resolve, reject, notify三种方法,promise主要有then方法。
下面我们先来构造一个简单的defer对象。

function defer () {    var tasks = [],        state = 'pending',        value,        reason;    return {        resolve: function (_value) {            if (tasks) {                value = _value;                tasks.forEach(function (task) {                    task(value);                })                tasks = undefined;                state = 'resolved';            } else {                if (state === 'resolved') {                    throw new Error('A promise should been resolved once.');                }             }        },        promise: {            then: function (callback) {                if (tasks) {                    tasks.push(callback);                } else {                    callback(value);                }            }        }    };};

再来看看相应的测试代码,看我们上面的这些是否OK?

var deferred = defer(),    promise  = deferred.promise;promise.then(function (value) {    console.log(value, value === 'zhangsan');});promise.then(function (value) {    console.log(value);});deferred.resolve('zhangsan');

现在可以在浏览器中试试了,当然你也可以选择使用 mocha 测试工具

describe('q0', function (done) {    var deferred,        promise;    beforeEach(function () {        deferred = Defer();        promise = deferred.promise;    })    it('the argument of then should be the value of defer.resolve', function (done) {        deferred.resolve('zhangsan');        promise.then(function (value) {            expect(value).to.eql('zhangsan');            done();        });    })    it('defer should be resolve once', function () {        deferred.resolve('zhangsan');        expect((function () {            deferred.resolve('lisi');        })).to.throw('A promise should been resolved once.');    })})

q1.js

q0.js实现了一个简单的promise,但与我们常用的promise并不一致,我们常用的promise是会返回一个promise让我们继续调用then,就如同promise.then().then(); 那我们接下来就来实现这一步,要实现这一步就必须满足以下这些条件:

  • then方法返回的必须是一个promise
  • 返回的这个promiseresolve值必须是上一个promise.then返回的值。

    promise.then(function(value){    return value1}).then(function(value1));
  • then函数返回的可以是一个value或一个promise。比如上一段代码返回的就是一个value。只不过是我们将其转成了一个promise

如何将一个value转为promise。我们先来构建一个ref函数

var ref = function (value) {    return {        then: function (callback) {            return callback(value);        }    }}

这种方式很好理解,就是将原本的callback(value)方式转换成ref(value).then(callback);

但是当我们在ref函数中传入promise就不应当将其转换了,因此在ref函数中加入一个判断:

var ref = function (value) {    if (value && typeof value.then === 'function') {        return value;    }    return {        then: function (callback) {            return callback(value);        }    }}

好了,这样就基本实现将value转为promise的操作,让我们更进一步实现ref(value).then().then这种方式的调用,很简单。将return callback(value)变成return ref(callback(value));

我们来试试ref函数是否可以顺利运行:

ref('zhangsan').then(function (value) {    console.log(value);    return 'lisi';}).then(function (value) {    console.log(value);})

接下来需要将defer函数中修改一下:

<!--首先是resolve函数-->    value = _value;    tasks.forEach(function (task) {        task(value);    })变为:    value = ref(_value);    tasks.forEach(function (task) {        // task(value);        value.then(task);    })<!--then函数-->    if (tasks) {        tasks.push(callback);    } else {        callback(value);    }变为:    var deferred = defer();    var callback = function (value) {        deferred.resolve(_callback(value));    };    if (tasks) {        tasks.push(callback);    } else {        value.then(callback);    }    return deferred.promise;

我们在看看对应的测试代码:

promise.then(function (value) {    console.log(value);    return 'lisi';}).then(function (value) {    console.log(value);});setTimeout(function () {    deferred.resolve('zhangsan');}, 3000)

相应的mocha测试代码:

it('the promise return a promise', function (done) {    deferred.resolve('zhangsan');    promise.then(function (value) {        expect(value).to.eql('zhangsan');        return 'lisi';    }).then(function (value) {        expect(value).to.eql('lisi');        done();    })})

完整q1.js代码具体在这里:

q2.js

defer不仅有resolve, 同时还有reject, notify等方法,promise.then也有相应的三个参数。接下来我们来实现reject

首先构造一个函数reject:

var reject = function (reason) {    return {        then: function (callback, errback) {            return ref(errback(reason));        }    };};

然后对应的then函数修改成如下:

var deferred = defer();var callback = function (value) {    deferred.resolve(_callback(value));};var errback = function (reason) {    deferred.resolve(_errback(reason));}if (tasks) {    tasks.push([callback, errback]);} else {    // value.then(callback, errback);    if (state === 'reject') {        value.then(errback);    } else if (state === 'resolved') {        value.then(callback);    }}

新增reject方法:

reject: function (reason) {    if (tasks) {        value = ref(reason);        tasks.forEach(function (task) {            value.then.apply(value, [task[1], task[0]]);        })        tasks = undefined;        state = 'rejected';    } else {        if (state === 'rejected') {            throw new Error('A promise should been rejected once.');        }     }},

修改resolve方法:

tasks.forEach(function (task) {    // task(value);    value.then.apply(value, task);})

下面来测一测reject方法是否ok

var deferred = defer(),    promise  = deferred.promise;setTimeout(function () {    deferred.reject('lisi');}, 2000);promise.then(function () {}, function (value) {    console.log(value);    return 'zhangsan';}).then(function (value) {    console.log(value);})

对应的mocha测试代码:

it('reject should work', function (done) {    setTimeout(function () {        deferred.reject('zhangsan');    }, 100);    promise.then(function () {}, function (value) {        expect(value).to.eql('zhangsan');        done();    })})

q3.js

接下来进行一些优化,promise.then一般来说都不会将三个参数函数都写出来,因此我们需要进行一下处理:

<!--then函数-->// 确保then参数不为空_callback = _callback || function (value) {    return value;}_errback = _errback || function (reason) {    return reject(reason);}

大家都知道promise是对未来执行的一种承诺,因此它的执行动作应该是未来的某一个时刻。而我们目前的代码都是在同一个执行周期里面执行,因此我们需要做一下处理。创建一个nextTick函数,将这些执行操作,放在nextTick中执行。

<!--nextTick-->// 不一定要是使用setTimeout,可以选择使用process.nextTickor setImmediate    var nextTick = function (callback) {        setTimeout(callback, 0);    };

因此将所有的callback执行放在nextTick中执行。此处改动较多,

<!--相关注释部分,就是改动地方-->'use strict';var ref = function (value) {    if (value && typeof value.then === 'function') {        return value;    }    return {        then: function (callback) {            // return ref(callback(value));            var deferred = defer();            nextTick(function () {                deferred.resolve(callback(value))            });            return deferred.promise;        }    }}var reject = function (reason) {    return {        then: function (callback, errback) {            // return ref(errback(reason));            var deferred = defer();            nextTick(function () {                deferred.resolve(errback(reason))            });            return deferred.promise;        }    }};// 不一定要是使用setTimeout,可以选择使用process.nextTick// or setImmediatevar nextTick = function (callback) {    setTimeout(callback, 0);};function defer () {    var tasks = [],        state = 'pending',        value,        reason;    return {        resolve: function (_value) {            if (tasks) {                value = ref(_value);                tasks.forEach(function (task) {                    // value.then.apply(value, task);                    nextTick(function () {                        value.then.apply(value, task);                    })                })                tasks = undefined;                state = 'resolved';            } else {                if (state === 'resolved') {                    throw new Error('A promise should been resolved once.');                }             }        },        reject: function (reason) {            if (tasks) {                value = ref(reason);                tasks.forEach(function (task) {                    // value.then.apply(value, [task[1], task[0]]);                    nextTick(function () {                        value.then.apply(value, [task[1], task[0]]);                    })                })                tasks = undefined;                state = 'rejected';            } else {                if (state === 'rejected') {                    throw new Error('A promise should been rejected once.');                }             }        },        promise: {            then: function (_callback, _errback) {                var deferred = defer();                _callback = _callback || function (value) {                    return value;                }                _errback = _errback || function (reason) {                    return reject(reason);                }                var callback = function (value) {                    deferred.resolve(_callback(value));                };                var errback = function (reason) {                    deferred.resolve(_errback(reason));                }                if (tasks) {                    tasks.push([callback, errback]);                } else {                    /*if (state === 'reject') {                        value.then(errback);                    } else if (state === 'resolved') {                        value.then(callback);                    }*/                    nextTick(function (){                        if (state === 'rejected') {                            value.then(errback);                        } else if (state === 'resolved') {                            value.then(callback);                        }                    })                }                return deferred.promise;            }        }    };}module.exports = defer;

q4.js

接下来我们来实现defer.notify。在defer中定义一个变量progresses用来保存相关的进度。在defer中定义一个方法:notify:

noitfy: function (progress) {    if (state === 'resolved' || state === 'rejected') {        return ;    }    progresses.push(progress);},

那何时进行notify呢,当然是在resolvereject之前执行,也就是在执行then方法的时候:

<!--then中新增-->  then: function (_callback, _errback, _notifyback) {    var deferred = defer();    _callback = _callback || function (value) {        return value;    }    _errback = _errback || function (reason) {        return reject(reason);    }    _notifyback  = _notifyback || function (progress) {        return progress;    }    var callback = function (value) {        deferred.resolve(_callback(value));    };    var errback = function (reason) {        deferred.resolve(_errback(reason));    }    nextTick(function () {        while(progresses.length) {            _notifyback(progresses.shift());        }    });    if (tasks) {        tasks.push([callback, errback]);    } else {        nextTick(function (){            if (state === 'rejected') {                value.then(errback);            } else if (state === 'resolved') {                value.then(callback);            }        })    }    return deferred.promise;}

那我们来测一测notify:

var deferred = defer(),promise  = deferred.promise;for (var i = 0; i < 10; i++) {    deferred.noitfy(i);}setTimeout(function () {    deferred.reject('lisi');}, 2000);promise.then(function () {}, function (value) {    console.log(value);    return 'zhangsan';}, function (progress) {    console.log(progress);}).then(function (value) {    console.log(value);})

相关的mocha测试代码:

it('notify should work', function (done) {    var spy = chai.spy(function (progress) {        console.log(progress);    });    setTimeout(function () {        deferred.reject('zhangsan');    }, 100);    for (var i = 0; i < 10; i++) {        deferred.noitfy(i);    }    promise.then(function () {}, function (value) {        expect(value).to.eql('zhangsan');        expect(spy).to.have.been.called.exactly(10);        done();    }, spy)})

q5.js

到上面为止,我们基本实现了一个完成的promise。这个promise已经可以使用,但是如果在执行回调的过程中出现error我们应当如何处理?很简单我们在下一级的promise中进行处理。

<!--在then函数中,如果发生错误,则进行deferred.reject-->var callback = function (value) {    // deferred.resolve(_callback(value));    var result;    try {        result = _callback(value)    } catch (e) {        deferred.reject(e);    } finally {        deferred.resolve(result);    }};var errback = function (reason) {    // deferred.resolve(_errback(reason));    var result;    try {        result = _errback(reason)    } catch (e) {        deferred.reject(e);    } finally {        deferred.resolve(result);    }}nextTick(function () {    while(progresses.length) {        // _notifyback(progresses.shift());        try {            _notifyback(progresses.shift());        } catch (e) {            deferred.reject(e);            break;        }    }});

相应的测试js代码:

var deferred = defer(),promise  = deferred.promise;setTimeout(function () {    deferred.resolve('lisi');}, 2000);promise.then(function(value) {    console.log(value);    throw 'zhangsan';}).then(function () {}, function (err) {    console.error(err);    done();})

mocha测试代码:

it('catch error in reject should work', function (done) {    setTimeout(function () {        deferred.resolve('lisi');    }, 10);    promise.then(function(value) {        expect(value).to.eql('lisi');        throw 'zhangsan';    }).then(function () {}, function (err) {        expect(err).to.eql('zhangsan');        done();    })})

q6.js

到目前为止,这个promise基本上是可以使用了,它的基本功能都是OK的。一个好的框架,当让不仅仅是只有这些基本功能。接下来我们为他们添加一些常用功能如:all done fail finally catch等这些常用方法。

<!--all-->function all (arr) {    if (Object.prototype.toString.call(arr, arr) === '[object Array]') {        var len     = arr.length;        var already = 0;        var deferred = defer();        // 确保所有promise已经完成        arr.forEach(function (promise) {            promise.then(function () {                console.log('first promise: ', already);                already++;                if (already === len) {                    deferred.resolve()                }            })        });        var result = [];        var resultDeferred = defer();        // 按序获取所有promise的值        deferred.promise.then(function () {            arr.forEach(function (promise) {                promise.then(function (value) {                    result.push(value);                })            })            resultDeferred.resolve(result);        });        return resultDeferred.promise;    } else {        var deferred = defer();        deferred.resole(arr);        return deferred.promise;    }}

完整的q6.js

'use strict';function Q () {}Q.defer = defer;Q.all   = all;Q.any   = any;var ref = function (value) {    if (value && typeof value.then === 'function') {        return value;    }    return {        then: function (callback) {            // return ref(callback(value));            var deferred = defer();            nextTick(function () {                deferred.resolve(callback(value))            });            return deferred.promise;        }    }}var reject = function (reason) {    return {        then: function (callback, errback) {            // return ref(errback(reason));            var deferred = defer();            nextTick(function () {                deferred.resolve(errback(reason))            });            return deferred.promise;        }    }};// 不一定要是使用setTimeout,可以选择使用process.nextTick// or setImmediatevar nextTick = function (callback) {    setTimeout(callback, 0);};var noopFn = function () {};function defer () {    var tasks      = [],        progresses = [],        state      = 'pending',        value,        reason,        _finallyback;    return {        resolve: function (_value) {            if (tasks) {                value = ref(_value);                tasks.forEach(function (task) {                    // value.then.apply(value, task);                    nextTick(function () {                        value.then.apply(value, task);                    })                })                tasks = undefined;                state = 'resolved';            } else {                if (state === 'resolved') {                    throw new Error('A promise should been resolved once.');                }             }            if (_finallyback) {                nextTick(function () {                    _finallyback && (_finallyback());                    _finallyback = undefined;                })            }        },        reject: function (reason) {            if (tasks) {                value = ref(reason);                tasks.forEach(function (task) {                    // value.then.apply(value, [task[1], task[0]]);                    nextTick(function () {                        value.then.apply(value, [task[1], task[0]]);                    })                })                tasks = undefined;                state = 'rejected';            } else {                if (state === 'rejected') {                    throw new Error('A promise should been rejected once.');                }             }            if (_finallyback) {                nextTick(function () {                    _finallyback && (_finallyback());                    _finallyback = undefined;                })            }        },        noitfy: function (progress) {            if (state === 'resolved' || state === 'rejected') {                return ;            }            progresses.push(progress);        },        promise: {            then: function (_callback, _errback, _notifyback) {                var self = this;                var deferred = defer();                _callback = _callback || function (value) {                    return value;                }                _errback = _errback || function (reason) {                    return reject(reason);                }                _notifyback  = _notifyback || function (progress) {                    return progress;                }                var callback = function (value) {                    // deferred.resolve(_callback(value));                    var result;                    try {                        result = _callback(value)                    } catch (e) {                        deferred.reject(e);                    } finally {                        deferred.resolve(result);                    }                };                var errback = function (reason) {                    // deferred.resolve(_errback(reason));                    var result;                    try {                        result = _errback(reason)                    } catch (e) {                        deferred.reject(e);                    } finally {                        deferred.resolve(result);                    }                }                nextTick(function () {                    while(progresses.length) {                        // _notifyback(progresses.shift());                        try {                            _notifyback(progresses.shift());                        } catch (e) {                            if (_catchback) {                                _catchback(e);                                catchback = undefined;                                return;                            }                            deferred.reject(e);                            break;                        }                    }                });                if (tasks) {                    tasks.push([callback, errback]);                } else {                    nextTick(function (){                        if (state === 'rejected') {                            value.then(errback);                        } else if (state === 'resolved') {                            value.then(callback);                        }                    })                }                if (_finallyback) {                    nextTick(function () {                        _finallyback && (_finallyback());                        _finallyback = undefined;                    })                }                return deferred.promise;            },            done: function (_callback) {                return  this.then(_callback, noopFn);            },            fail: function (_errback) {                return this.then(noopFn, _errback);            },            'finally': function (finallyback) {                _finallyback = finallyback;                 },            'catch': function (catchback) {                // _catchback = catchback;                return this.then(noopFn, catchback);            }        }    };}function all (arr) {    if (Object.prototype.toString.call(arr, arr) === '[object Array]') {        var len     = arr.length;        var already = 0;        var deferred = defer();        // 确保所有promise已经完成        arr.forEach(function (item) {            ref(item).then(function () {                already++;                if (already === len) {                    deferred.resolve()                }            })        });        var result = [];        var resultDeferred = defer();        // 按序获取所有promise的值        deferred.promise.then(function () {            arr.forEach(function (item) {                ref(item).then(function (value) {                    result.push(value);                })            })            resultDeferred.resolve(result);        });        return resultDeferred.promise;    } else {        var deferred = defer();        deferred.resole(arr);        return deferred.promise;    }}function any (arr) {    var deferred = defer();    arr.forEach(function (item) {        ref(item).then(function (value) {            deferred.resolve(value);        })    })    return deferred.promise();}module.exports = Q;

mocha的相关测试代码:

it('promise done should work', function (done) {    setTimeout(function () {        deferred.resolve('lisi');    }, 10);    promise.done(function (value) {        expect(value).to.eql('lisi');        done();    })})it('promise fail should work', function (done) {    setTimeout(function () {        deferred.reject('lisi');    }, 10);    promise.fail(function (value) {        expect(value).to.eql('lisi');        done();    })})it('catch error in fail method', function (done) {    setTimeout(function () {        deferred.resolve('lisi');    }, 10);    promise.done(function (value) {        expect(value).to.eql('lisi');        throw 'zhangsan';    }).fail(function (value) {        expect(value).to.eql('zhangsan')        done();    })})it('promise finally should work', function (done) {    var deferred1 = Defer();    var deferred2 = Defer()    setTimeout(function () {        deferred1.resolve('lisi');        deferred2.reject('lisi');    }, 10);    deferred1.promise['finally'](function () {        done();    })    /*deferred2.promise['finally'](function (value) {        expect(value).to.eql('lisi');        done()    })*/})it('catch error in catch method', function (done) {    setTimeout(function () {        deferred.resolve('lisi');    }, 10);    promise.done(function (value) {        expect(value).to.eql('lisi');        throw 'zhangsan';    })['catch'](function (err) {        expect(err).to.eql('zhangsan')        done();    })})it('Q.all method should work', function (done) {    var d,        arr = [];    for (var i = 0; i < 10; i++) {        d = Defer();        d.resolve(i)        arr.push(d.promise);    }    d = Defer();    arr.push(d.promise);    setTimeout(function () {        d.resolve('end');    }, 100);    Q.all(arr).then(function (value) {        expect(value.length).to.eql(11);        done();    })})
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 魅蓝2开不开机怎么办 魅族手机主键没反应怎么办 魅族手机主键失灵怎么办 手机4g网络不稳定怎么办 华为m9收不到手机信息怎么办 华为手机wifi信号弱怎么办 手机连接wifi信号差怎么办 华华为p10信号不好怎么办 烟没拆封受潮了怎么办 和亲儿子发生了性关系怎么办 无心磨磨出来圆度不好怎么办 中国人在越南办结婚证怎么办? 无线网被限速了怎么办 联通大王卡上传速度慢怎么办 小米手机下载视频速度慢怎么办 大疆御air脚架断了怎么办 大疆飞行器线断了怎么办 移动校园卡套餐到期后怎么办 流量年包到期了怎么办 家里无线网信号不好怎么办 无线网光信号红灯了怎么办 机顶盒获取不了lp地址怎么办 32内存卡丢了怎么办 手机上的相机找不到了怎么办 有刘鑫这样的闺蜜该怎么办 电脑开机网络初始化失败怎么办 电脑放音乐没有声音怎么办 苹果手机gprs信号弱怎么办 苹果导航gprs信号弱怎么办 au没有波形 没有声音怎么办 屏幕驱动板坏了怎么办 安吉星流量超了怎么办 网络被伪基站覆盖怎么办 骨头渣子卡嗓子里怎么办 执法仪记录仪关不了机怎么办 执法记录仪开不了机怎么办 华为警务通丢了怎么办 华德安执法记录仪死机怎么办 行车仪内存满了怎么办 海康威视摄像头没有通道怎么办 电脑屏膜变大了怎么办