顾名思义,promise中文意思就是承诺,也就是如今实现不了未来•••••,可是未来这玩意谁说的准呢。就像你去泡妞,你可能许下各类诺言,但能不能实现,彻底取决于你这人靠不靠谱。好在计算机不是人,不是人,不是人,••••正由于不是人,因此它许下的承诺,它就必定会给你一个结果。
等待承诺实现的过程当中很漫长,因此你能够作一些其它的事情,不必总是堵在这一条道上,也就是异步。打个比方,你打电话给饭店老板叫了个外卖,老板告诉你,10分钟后送过去,也就是说老板给了你一个承诺,因而你等啊等,这中间又去上了个厕所,玩了会手机••••••,这就是异步,老板给的承诺并无妨碍你干其它的事情。OK,扯淡结束。angularjs
为了实现异步,通常要设置一个回调函数es6
setTimeout(function(){ console.log(1); setTimeout(function(){ console.log(2); setTimeout(function(){ console.log(3); setTimeout(function(){ console.log(4); setTimeout(function(){ console.log(5); },500) },400) },300) },200) },100);
••••••有没有一种想死的感受!
promise最大优点就是第一消灭了这种回调地狱,第二增长了错误捕获,像下面这种,ajax
promis.then(function (response) { //do something; }, function (reason) { //get error }).then(function (response) { //do something; }, function (reason) { //get error }).then(function (response) { //do something; }, function (reason) { //get error });
若是不作错误处理则更清晰chrome
promis.then(function (response) { //do something; }).then(function (response) { //do something; }).then(function (response) { //do something; }).then(function (response) { //do something; });
它使得异步的代码看起来像是在同步执行,大大加强了代码的可读性。
美中不足的是你得写一堆的.then(function(){},function(){}),可是和回调地狱相比,忍了。
在ES7中会有号称是异步的终极解决方案,async和await,那是后话。promise
前面说了,计算机不是人,因此它许下的承诺,它必定会给你一个结果,无论这个承诺的结果是接受仍是拒绝。
因此,第一,promise必定会返回一个结果。
第二,这个结果是不可逆的,你只能接受,本质是由于promise的状态不可逆,一旦它变成了resolve或者reject,你休想再让你变成pending,不然,它要会说话,确定回你的只有一个字,滚!
第3、promise的结果何时返回,你说了不算,你去泡妞的时候,妞也不必定当场就答应你吧,或许想个3、五天也说不定,这个主动权不是掌握在你手中的。
第4、ES6的promise执行过程当中,你是没法得到执行的进度的,到底它如今是pending仍是resolve,仍是reject。就好像妞和她的闺蜜探讨要不要接受你,你是打听不到的。固然并非彻底不能,例如angularjs的$q实现一个notify方法,能够获取到执行进度的通知。
最后说一点儿你的权力,你能决定的是在何时去取promise的结果,也就是调用then方法的时间,就好像你天天追着妞问,你想好没有••••••,妞这个时候会有三种回答,一是答应你,二是拒绝你,三是还得再想一想,XXXX时候再告诉你••••,也就说这TMD又是一个承诺•••••。
咳、咳,如今开始必须严肃点,毕竟技术是一件严肃的事情。浏览器
说白了,promise就是一个对象,一个只能经过then方法来取得它上面的值的对象。
在es6中,你只要大喊一句,妞,给我个承诺,它就会给你一个promise,就像下面这样:闭包
var promise = new Promise(function(resolve,reject){ //do something; })
而后你就能够调用它的then方法去取值,那么从这个角度看,这个构造函数必定是返回了一个带有then方法的对象。另外还有状态,状态的变化不可逆。再加上些其它的方法,如all、catch•••,不过不要着急,咱们一步一步来意淫出这个漂亮的妞••••
一、一般状况,咱们使用回调一个函数内执行另一个函数:异步
function doSomething(callback){ console.log("do something"); callback(); } doSomething(function(){ console.log("a"); });
二、可是在使用promise时,咱们是用then方法去取结果,而promise就是个对象,那么上面的代码看起来应该这样写:async
function doSomething(){ console.log("a"); return { then: function(callback){ var value = 1; callback(value); } } } doSomething().then(function(res){ console.log(res); });
在这里,咱们调用dosomething函数时,返回了一个带有then方法的对象,而后在then方法回调中去执行,如今看来是否是有那么点样子了,时刻记得两件事,对象, then方法。
三、在ES6中Promise是一个构造函数,这简单,给这个dosomething换个名字,函数
function Promise(){ this.then = function(callback){ var value = 1; callback(value); } }
在实例化promise的时候,要传一个函数进去,这也简单
function Promise(fn){ this.then = function(callback){ callback(); } }
实例化传入的函数fn中(下文中的fn都是指代这个匿名函数),你会传入2个参数,一个叫resolve,另外一个叫reject,为了简单起见,咱们不考虑reject,它的道理和resolve是同样的。那么就像这样:
var promise = new Promise(function(resolve){ var value = 1; resolve(value); })
即然传了一个fn函数进去,那么在实例化过程当中,这个函数必定会在某个时刻执行。执行时,它又会接收到一个参数resolve,这个resolve必定是一个函数,这点从上面就能够很明显的看出来,resolve在实例化时执行了,并且接收到了一个参数,在这里是变量value。那么Promise函数内部极可能是这样:
function Promise(fn){ function resolve(value){} this.then = function (onResolved) {}; fn(resolve); }
为了看起来更直接,这里咱们把调用then方法传的第一个函数就叫作onResolved,那么接下来咱们应该考虑在实例化的时候,还有什么事情要作,在then方法的回调函数中咱们但愿获得promise的值,这个值是在fn函数调用后被resolve函数运算后获得的,最终要在onResolved函数中拿到,也就是说,咱们必须在resolve中将这个值传递给onResolved,迂回一下:
function Promise(fn) { var callback = null; function resolve(value) { callback(value); } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
可是这里有一个问题,就是咱们调用resolve方法时,尚未调用过then方法,所以callbak是null,浏览器报错:callback is not a function,这里hack下,让resolve方法的执行在then以后。
function Promise(fn) { var callback = null; function resolve(value) { setTimeout(function(){ callback(value); },0) } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
执行一下,
var promise = new Promise(function(res){ var value = 2; res(2); }); promise.then(function(res){ console.log(res); })
OK,成功的输出。目前为止,promise的轮廓算是被咱们意淫出来了。
四、promise是有状态的,并且状态不可逆,一样的为了简单起见,我先来搞定从pending变到resolved,那么rejected也同样。仔细想下,执行了resolve方法后能够获得一个resolved状态的值,那么必然在resolve方法中会去改变promise的状态,而且获得这个值,那么代码貌似应该这样写:
function Promise(fn) { var state = 'pending'; function resolve(newValue) { state = 'resolved'; callback(newValue); } this.then = function(onResolved) { callback = onResolved; }; fn(resolve); }
这里咱们先把setTimeout这家伙给干掉了,由于咱们加入了状态,也就意味咱们是想经过状态的变化来知道能不能获得值,那么问题来了,咱们不知道状态啥时候变,就像你不知道你要泡的妞啥时候答应你同样,你只能追问,万一妞没想好,她极可能再给你一个承诺,就是那个该死的XXX时候再告诉你,不过好歹她也算给了你一个等待的机会,而咱们如今要作的就是创造这么个机会。
function Promise(fn) { var state = 'pending'; var value; var deferred; function resolve(newValue) { value = newValue; state = 'resolved'; if(deferred) { handle(deferred); } } function handle(onResolved) { if(state === 'pending') { deferred = onResolved; return; } onResolved(value); } this.then = function(onResolved) { handle(onResolved); }; fn(resolve); }
这里引入了另一个函数handle,至此能够说promise的最关键的东西咱们已经看到了,妞的身材逐渐显现。又扯远了•••••
仔细看下除了handle咱们还引入两个变量value和deferred,先从最简单的来:
value的做用很简单,在构造函数内它是一个全局变量,起到一个桥梁做用,就是为了在handle函数内能取到newValue的值,而newValue就是fn函数里的那个结果。
handle咱们估且能够认为它是妞的一个管家,它会去替咱们询问妞有没有想好,也就是去判断当前这个承诺的状态,再决定怎么作。
deferred咱们估且能够这样理解,它就是管家的一个记事本,你隔三差五的去问,它老人家不得记下来,若是一不当心忘了,那就悲催了。
这下无论是同步仍是异步,咱们随时能够在then方法中去取值,若是值没有被resolve,也就是说状态没发生变化,deferred将给咱们记录下这件事,等到resolve的那个时间点把值传给then方法中那个回调函数,onResolved。
在这里请默念一百遍handle,defer,再接着往下看,我保证他们会让你困惑。
五、回到最初,为何要用promise,想一想回调地狱,再想一想promise是怎么解决的,那就是then方法链式调用。
可以实现链式调用,也就是说then方法返回的值也必定是个promise,这样你才能.then,.then的一直写下去。废话不说,没代码说个毛:
function Promise(fn) { var state = 'pending'; var value; var deferred = null; function resolve(newValue) { value = newValue; state = 'resolved'; if(deferred) { handle(deferred); } } function handle(handler) { if(state === 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { return new Promise(function(resolve) { handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); }
这下换个姿式,咱们先啃硬货。咱们让then方法返回了一个promise,并且这个promise实例化时传入的函数里调用了handle函数,传入了一个对象,onResolved很显然就是then方法里第一个函数,没什么可说的。关键是这handle和resolve是哪一个?思考1分钟。
这里咱们用setTimeout简单模拟一个异步,拿一个then看下,发生了什么:
var promise = new Promise(function(resolve){ setTimeout(function(){ resolve(1); },3000) }); promise.then(function(res){ console.log(res); })
首先咱们去new一个promise,在实例化的过程当中,调用了传进的那个函数,3秒后才能执行到resolve,紧接着调用了它的then方法,这个时候因为promise的状态没变,确定取不到值,好在then方法会返回个promise,因而又执行了一次promise的实例化过程。这里没法回避的就是做用域的问题,这个关系到handle函数执行在哪一个环境中,参数的到底从哪一个地方获取到,另外就是强大的闭包。相关知识不解释。
为了看的更清楚,咱们加入一些标记,到chrome的控制台中调试下:
var count = 0; function Promise(fn) { var state = 'pending'; var value; var deferred = null; var scope = ++count; function resolve(newValue) { value = newValue; state = 'resolved'; console.log("resolve: I am in " +scope); if(deferred) { handle(deferred); } } function handle(handler) { console.log("handle: I am in " +scope); if(state === 'pending') { deferred = handler; return; } if(!handler.onResolved) { handler.resolve(value); return; } var ret = handler.onResolved(value); handler.resolve(ret); } this.then = function(onResolved) { console.log("then: I am in " + scope); return new Promise(function(resolve) { console.log("then promise: I am in " + scope); handle({ onResolved: onResolved, resolve: resolve }); }); }; fn(resolve); } var promise = new Promise(function(resolve){ setTimeout(function(){ resolve(1); },3000) }); promise.then(function(res){ console.log(res); });
加入的scope是为了监视做用域的变化,以间接反应出咱们调用handle时是在哪一个做用域上查询到的,此外咱们还须要监视state和deferred的变化。
主要看then调用以后,废话不说上图:
5-一、在执行then方法的时候,scope=1,state,deferred不可用。因为模拟了异步,这个时候第一个promise的resolve方法并无执行,这里模拟了3秒,实际状况下,好比ajax取数据时,咱们并不知道这个准确的时间,就像开始时说的,这妞啥时候答应你,主动权不在你手中,由妞说了算。
5-二、接下来去实例化then方法建立的这个promise,scope = 2,state=”pending”,deferred=null。
5-三、在实例化完成以后,此时去执行fn函数,scope=1,state,deferred不可用。
第一,函数的做用域是在定义时就生成的,而不是在调用的时候。第二个promise定义的时候,是在第一个promise做用域上,这样即便它被return了出去,因为闭包的特性,仍读取的是第一个做用域上值,因此这里的handle一定是第一个promise的handle。而resolve则不一样,它是做为行参传递了进来,因此这里的resolve是第二个promise的resolve。
5-四、进入handle时,scope = 1,state =” pending”,deferred保存了参数。
5-五、3秒时间到,第一个promise里的resolve被执行了,也就是说拿到告终果,这时候,scope=1,state = “resolved”,deferred保存着刚才传进来的那个对象,再次进入handle函数。
5-六、scope=1,state = “resolved”,deferred求值为true,所以确定会继续执行。下面添加的这段代码在这里也就很清楚了,假如then方法中没有传进来的onResolved函数,这里的value将直接交给下一个then方法中的onResolved函数使用,避免一些无聊的人像这样去调用:
promise.then().then().then(function(res){ console.log(res); })
正常人都会让value在onResolved函数中接收到,而后ret就是onResolved函数的返回值,这里没有return回的值,因此ret确定是undefined。
5-七、scope=2,state = “resolved”,deferred=null。这里的resolve是第个promise的resolve,因此定义的时候就是在做用域2上,若是后面再调用then方法,生成新的promise,这时就会将undefined做为第二个promise的值传递下去。
这里再次强调一下,handle方法和deferred是核心所在,其背后的精髓无非仍是做用域和闭包的巧妙设计。变量的读取一定先从自身所处做用域开始,若是自身做用域上读不到,才会一级一级向上访问。
六、意淫到这里基本上核心的东西就差很少了,下面咱们来加上reject时的状况,直接上代码:
function Promise(fn) { var state = 'pending'; var value; var deferred; this.then = function (onResolved, onRejected) { return new Promise(function (resolve, reject) { handle({ onResolved: onResolved, onRejected: onRejected, resolve: resolve, reject: reject }); }); }; function resolve(newValue) { if (newValue && typeof newValue.then === 'function') { newValue.then(resolve); return; } state = 'resolved'; value = newValue; if (deferred) { handle(deferred); } } function reject(reason) { state = 'rejected'; value = reason; if (deferred) { handle(deferred); } } function handle(handler) { if (state === 'pending') { deferred = handler; return; } var handlerCallback; if (state === 'resolved') { handlerCallback = handler.onResolved; } else { handlerCallback = handler.onRejected; } if (!handlerCallback) { if (state === 'resolved') { handler.resolve(value); } else { handler.reject(value); } return; } var ret; try { ret = handlerCallback(value); } catch (e) { handler.reject(e); return; } handler.resolve(ret); } fn(resolve); }
状况基本和resolve是同样的,resolve函数中加的if判断只为了对付返回值是promise的状况下仍然能够经过后续的then方法取到值,handle中的try/catch块的加入使得能够捕获到promise及then方法回调中的错误,至于then方法的改变,看不懂的话自宫吧,你是女人当我没说。
固然这个promise只是一个基本的实现,依然很脆弱,但基本上能够说有了一轮廓,剩下的部位各位看官本身添加,好比promise的all ,race,catch等。某种意义上说,它们也只是then方法的语法糖。
http://www.mattgreer.org/arti...,本文代码出处,算是学习后的一点体会,水平有限,理解不对之处,还请指正。