最终答案在一个类库里,地址 https://github.com/yahoo/ypromise 这个类库也有问题,就是下面这道面试题在IE9里实现不一致,类库里还是用了setTimeout。去年尝试用setTimeout(,0)来实现Promise,见Promise的实现原理 ,最后以失败告终。今天前端leader在群里放了一组面试题,最后一题
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
romise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。因此,应当先输出 5,然后再输出 4 。最后在到下一个 tick,就是 1 。
“2 3 5 4 1”
补充解释是最后一题 Promise 的 4 在 1 前面输出是因为 Promise.then()里面的回调属于 microtask, 会在当前 Event Loop 的最后执行, 而 SetTimeout 内的回调属于 macrotask, 会在下一个 Event Loop 中执行。
这时候忽然发现用setTimeout来实现貌似有问题,然后东哥说当然不能用settimeout实现。promise依赖通知机制,不依赖时间,里面使用了观察者模式。这又给了我新的思路,去
github上搜一下Promise,有一些写好的库,关键词加上中文二字,也会发现简易实现。迷你书提到了一些较好的Promsie实现,真的有不用settimeout来实现的,好好看一下。
找到了另外一篇,promise异步编程的原理 担心作者删掉,就在这儿再贴一份(原文有错误,我这儿改正了),也可参考另外一篇。这篇也存在很大的问题,就是在只能用异步的方法,不能用同步。到底去哪找靠谱的实现呢。
要实现promise对象,首先要考虑几个问题:
1.promise构造函数中要实现异步对象状态和回调函数的剥离,并且分离之后能够还能使回调函数正常执行
2.如何实现链式调用并且管理状态
首先是构造函数:
//全局宏定义 var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; //Promise构造函数 function Promise(fn){ var self = this; self.state = PENDING;//初始化状态 self.value = null;//存储异步结果的对象变量 self.handlers = [];//存储回调函数,这里没保存失败回调函数,因为这是一个dome //异步任务成功后处理,这不是回调函数 function fulfill(result){ if(self.state === PENDING){ self.state = FULFILLED; self.value = result; for(var i=0;i<self.handlers.length;i++){ self.handlers[i](result); } } } //异步任务失败后的处理, function reject(err){ if(self.state === PENDING){ self.state = REJECTED; self.value = err; } } fn&&fn(fulfill,reject); };
构造函数接受一个异步函数,并且执行这个异步函数,修改promise对象的状态和结果。
回调函数方法then:
//使用then方法添加回调函数,把这次回调函数return的结果当做return的promise的resolve的参数 Promise.prototype.then = function(onResolved, onRejected){ var self = this; return new Promise(function(resolve, reject){ var onResolvedFade = function(val){ var ret = onResolved?onResolved(val):val;//这一步主要是then方法中传入的成功回调函数通过return来进行链式传递结果参数 if(ret instanceof Promise){//回调函数返回值也是promise的时候 ret.then(function(val){ resolve(val); }); } else{ resolve(ret); } }; var onRejectedFade = function(val){ var ret = onRejected?onRejected(val):val; reject(ret); }; self.handlers.push(onResolvedFade); if(self._status === FULFILLED){ onResolvedFade(self._value); } if(self._status === REJECTED){ onRejectedFade(self._value); } }); }
通过上面的代码可以看出,前面提出的2个问题得到了解决,1.在promise对象中有3个属性,state,value,handlers,这3个属性解决了状态和回调的脱离,并且在调用then方法的时候才将回调函数push到handlers属性上面(此时state就是1,可以在后面的代码中执行onResolve)2.链式调用通过在then方法中返回的promise对象实现,并且通过onResolvedFade将上一个回调的返回值当做这次的result参数来执行进行传递。
测试代码:
function async(value){ var pms = new Promise(function(resolve, reject){ setTimeout(function(){ resolve(value);; }, 1000); }); return pms; } async(1).then(function(result){ console.log('the result is ',result);//the result is 2 return result; }).then(function(result){ console.log(++result);//2 });