05月08, 2014

教你一步步实现一个Promise

Promise我想现在大家都非常熟悉了,主要作用就是解决异步回调问题,这里简单介绍下。

Promise规范是CommonJS规范之一,而Promise规范又分了好多种,比如 Promises/A、Promises/B、Promises/Kiss等等

有兴趣的可以到这多了解一些 http://wiki.commonjs.org/wiki/Promises

现在比较流行的是Promise/A规范,人们对它的完善和扩展,逐渐形成了Promise/A+规范,A+已脱颖而出。

说到这里规范是什么,可以去这里了解下

http://promises-aplus.github.io/promises-spec/

http://hussion.me/2013/10/19/promises-a/

现在已有浏览器内置支持Promise,它的api语法可以在这里查看

http://www.html5rocks.com/zh/tutorials/es6/promises/#toc-api

可以看到它的api并不多,其实规范也不多,我觉的大致抓住几个重要的点就够了,

1、promise有三种状态,等待(pending)、已完成(fulfilled)、已拒绝(rejected)

2、promise的状态只能从“等待”转到“完成”或者“拒绝”,不能逆向转换,同时“完成”和“拒绝”也不能相互转换

3、promise必须有一个then方法,而且要返回一个promise,供then的链式调用,也就是可thenable的

4、then接受俩个回调(成功与拒绝),在相应的状态转变时触发,回调可返回promise,等待此promise被resolved后,继续触发then链

知道这几个重要的特点,我们就可以参考浏览器内置的api来实现了,

我们可以不必太受规范约束,先按照自己的想法来就好了。

promise的使用大致如下

var promise = new Promise(function(resolve, reject) {
    setTimeout(function(){
        resolve("val")
    });
});
promise.then(onFulfilled,onRejected).then(onFulfilled,onRejected)

主要思路就是我们可以直接对返回的promise对象进行操作,比如then,传入回调,

这里的函数并不会立即执行,而是加入队列,等待未来的某个时间resolve时被触发执行。

有了以上说明,就可以来实现了

首先定义三个状态

var PENDING = undefined, FULFILLED = 1, REJECTED = 2;

然后实现Promise构造函数,此函数接受一个函数参数,函数参数接受俩个我们提供的方法resolve与reject,

供使用者在未来的某个时间里调用触发执行我们的队列,这里还要初始下当前的状态,传递的值,

以及then时保存到的队列。

大概像下面这样

var Promise = function(resolver){
    if (!isFunction(resolver))
        throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");
    if(!(this instanceof Promise)) return new Promise(resolver);

    var promise = this;
    promise._value;
    promise._reason;
    promise._status = PENDING;
    promise._resolves = [];
    promise._rejects = [];

    var resolve = function(value){
        //状态转换为FULFILLED 
        //执行then时保存到_resolves里的回调,
        //如果回调有返回值,更新当前_value
    }
    var reject = function(reason){
        //状态转换为REJECTED
        //执行then时保存到_rejects里的回调,
        //如果回调有返回值,更新当前_rejects
    }

    resolver(resolve,reject);
}

有了这个,我们在实现一个then就ok了,

then里要做的就是返回一个promise供then的链式调用,

而且promise.then(onFulfilled,onRejected)时,我们要判断当前promise的状态,

如果是pending则把onFulfilled与onRejected添加到_resolves与_rejects数组里,

否则的话根据状态,直接触发回调,这里要注意的是,如果返回的是promise,我们要等到此promise被resolves时,触发then链的下一个promise执行。

代码大概是这样

Promise.prototype.then = function(onFulfilled,onRejected){
    var promise = this;
    // 每次返回一个promise,保证是可thenable的
    return Promise(function(resolve,reject){
        function callback(value){
          var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
          if(isThenable(ret)){
              // 根据返回的promise执行的结果,触发下一个promise相应的状态
            ret.then(function(value){
               resolve(value); 
            },function(reason){
               reject(reason);
            });
          }else{
            resolve(ret);
          }
        }
        function errback(reason){
            reason = isFunction(onRejected) && onRejected(reason) || reason;
            reject(reason);
        }
        if(promise._status === PENDING){
               promise._resolves.push(callback);
               promise._rejects.push(errback);
           }else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行
               callback(promise._value);
           }else if(promise._status === REJECTED){
               errback(promise._reason);
           }
    });
}

这里说明下

var isThenable = function(obj){
  return obj && typeof obj["then"] == "function";
}

也就是说返回的对象带有then方法,我们就当作promise对象

到这里我们主要的工作就完成了,其他的all,race等方法都很简单,具体可以到这里看完整的实现

https://github.com/ygm125/promise

下面我们来做几个例子来看下效果

var getData100 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("100ms");
        },100);
    });
}

var getData200 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("200ms");
        },200);
    });
}

getData100().then(function(data){
    console.log(data); // 100ms
    return getData200();
}).then(function(data){
    console.log(data); // 200ms
    return data + data;
}).then(function(data){
    console.log(data) // 200ms200ms
});

当然可以直接getData100().then(getData200).then(function(val){})

then可以只传一个,接受成功的回调,也可以用catch方法,接受失败的回调,

catch是then的一个语法糖,相当于promise.then(undefined, onRejected)

也可以用all来并行执行

Promise.all([getData100(),getData200()]).then(function(value){
    console.log(value) // ["100ms","200ms"]
});

结果的顺序与传入的顺序相同。

我们也可以直接创建一个以obj为值的成功状态的promise,

Promise.resolve("FULFILLED").then(function(val){
    console.log(val) // FULFILLED
});

实现都相当简单,看代码就懂。

这里也可以做一些好玩的,比如创建一个delay方法

Promise.prototype.delay = function(ms){
    return this.then(function(val){
        return Promise.delay(ms,val);
    })
}

Promise.delay = function(ms,val){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(val);
        },ms);
    })
}

我们可以每隔多少毫秒执行一些操作

Promise.delay(1000).then(function(){
    // 一些操作
}).delay(1000).then(function(){
    // 一些操作
})

我们也可以包装一个循环,执行多少次,每次延迟多少秒执行什么操作

var len = 0,
    words = "你好,你是谁?";

function count(num,ms,cb){
    var pro = Promise.resolve();
    for (var i = 0; i < num; i++) {
        pro = pro.delay(ms).then(function(v){
            return cb(v);
        });
    };
}

count(words.length,800,function(){
    var w = words.substr(0,++len);
    console.log(w);
})

更多的东西等你来实现~

本文链接:https://gmiam.com/post/shi-xian-ge-promise.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。