JavaScript中的异步由来已久,不管是定时函数,事件处理函数仍是ajax异步加载都是异步编程的一种形式,咱们如今以nodejs中异步读取文件为例来编写一个传统意义的异步函数:node
var fs = require('fs'); function readJSON(filename,callback){ fs.readFile(filename,'utf8',function(err,res){ if(err){ return callback(err,null); } try{ var res = JSON.parse(res); }catch(ex){ callback(ex) } callback(null,res); }); }
若是咱们想异步读取一个json文件,它接受2个参数,一个文件名,一个回调函数。文件名必不可少,关键就在这个callback上面了,当咱们要执行这个readJSON函数时,要本身构造想要的回调函数,可是在readJSON函数内部传递callback时候不知道他的参数,显然是不友好的。下面在看一种异步编程的代码:jquery
fs.readFile('file1.txt','utf8',function(err,res){ fs.readFile('file2.txt','utf8',function(err,res){ fs.readFile('file2.txt','utf8',function(err,res){ console.log(res); }); }); });
这里嵌套了3个异步回调函数,他们的执行时刻都是不可预测的而且这样写代码也不符合普通程序的执行流程。ajax
因此,问题来了,promise提供了一个解决上述问题的模式。编程
promise是对异步编程的一种抽象。它是一个代理对象,表明一个必须进行异步处理的函数返回的值或抛出的异常。也就是说promise对象表明了一个异步操做,能够将异步对象和回调函数脱离开来,经过then方法在这个异步操做上面绑定回调函数。json
下面介绍具体API,这里遵循的是commonJS promise/A+规范。promise
promise有3种状态:pending(待解决,这也是初始化状态),fulfilled(完成),rejected(拒绝)。框架
promise惟一接口then方法,它须要2个参数,分别是resolveHandler和rejectedHandler。而且返回一个promise对象来支持链式调用。dom
promise的构造函数接受一个函数参数,参数形式是固定的异步任务,举一个栗子:异步
function sendXHR(resolve, reject){ var xhr = new XMLHttpRequest(); xhr.open('get', 'QueryUser', true); xhr.onload = function(){ if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304){ resolve(xhr.responseText); }else{ reject(new Error(xhr.statusText)); } }; xhr.onerror = function(){ reject(new Error(xhr.statusText)); } xhr.send(null) }
要实现promise对象,首先要考虑几个问题:async
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(Promise.isPromise(ret)){//回调函数返回值也是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 });
总之,不一样框架对promise的实现大同小异,上面的代码是ECMASCRIPT6标准的promise简单实现。jquery和其余框架也有实现,不过据说jquery的实现很糟糕- -!