Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval、DOM事件机制、ajax,通过传入回调函数实现控制反转。异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题。详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行。
request = function (url, cb, eb) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) { cb(xhr.responseText); } else { eb( new Error({ message: xhr.status })); } } }; xhr.open( 'get' , url, true ); xhr.send( null ); } |
这个例子中程序要依次处理data1、data2、data3,嵌套太多可读性太差
request = function (url) { var def = new Deferred(); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) { def.resolve(xhr.responseText) } else { //简化ajax,没有提供错误回调 def.reject( new Error({ message: xhr.status })); } } }; xhr.open( 'get' , url, true ); xhr.send( null ); return def.promise; } request( 'data1.json' ).then( function (data1) { console.log(data1); //处理data1 return request( 'data2.json' ); }).then( function (data2) { console.log(data2); //处理data2 return request( 'data3.json' ); }, function (err) { console.error(err); }).then( function (data3) { console.log(data3); alert( 'success' ); }, function (err) { console.error(err); }); |
这个例子中程序需要请求data1、data2、data3数据,得到三个数据后才进行下一步处理。数据并不需要串行请求,但我们的代码却需要串行执行,增加了等待时间。
//并行逻辑串行执行 request( 'data1' , function (data1) { request( 'data2' , function (data2) { request( 'data3' , function (data3) { console.log(data1, data2, data3); //处理全部数据 alert( 'success' ); }, function (err) { console.error(err); }); }, function (err) { console.error(err); }); }, function (err) { console.error(err); }); |
Promise机制
Promise机制便是上述问题的一种解决方案。与他相关的规范有PromiseA和PromiseA+,PromiseA中对Promise进行了整体描述,PromiseA+对A进行了补充,在then函数的行为方面进行了更加详尽的阐述。
then
method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.跟promise交互的主要方式是通过他的then方法来注册回调函数去接收promise的最终结果值或者是promise不能完成的原因。我们可以简单总结一下规范。每个promise都有三个状态:pending(默认)、fulfilled(完成)、rejected(失败);默认状态可以转变为完成态或失败态,完成态与失败态之间无法相互转换,转变的过程是不可逆的,转变一旦完成promise对象就不能被修改。通过promise提供的then函数注册onFulfill(成功回调)、onReject(失败回调)、onProgres(进度回调)来与promise交互。Then函数返回一个promise对象(称为promise2,前者成为promise1),promise2受promise1状态的影响,具体请查看A+规范。
上两个规范中并没有说明promise的状态如何改变,大部分前端框架中使用Deferred来改变promise的状态(resolve()、reject())。二者关系请看下图。
这里根据规范,我们实现一下promise
Promise = function () { this .queue = []; this .value = null ; this .status = 'pending' ; // pending fulfilled rejected }; Promise.prototype.getQueue = function () { return this .queue; }; Promise.prototype.getStatus = function () { return this .status; }; Promise.prototype.setStatus = function (s, value) { if (s === 'fulfilled' || s === 'rejected' ) { this .status = s; this .value = value || null ; this .queue = []; var freezeObject = Object.freeze || function (){}; freezeObject( this ); // promise的状态是不可逆的 } else { throw new Error({ message: "doesn't support status: " + s }); } }; Promise.prototype.isFulfilled = function () { return this .status === 'fulfilled '; }; Promise.prototype.isRejected = function() { return this.status === ' rejected '; } Promise.prototype.isPending = function() { return this.status === ' pending '; } Promise.prototype.then = function(onFulfilled, onRejected) { var handler = { ' fulfilled ': onFulfilled, ' rejected ': onRejected }; handler.deferred = new Deferred(); if (!this.isPending()) {//这里允许先改变promise状态后添加回调 utils.procedure(this.status, handler, this.value); } else { this.queue.push(handler);//then may be called multiple times on the same promise;规范2.2.6 } return handler.deferred.promise;//then must return a promise;规范2.2.7 }; var utils = (function(){ var makeSignaler = function(deferred, type) { return function(result) { transition(deferred, type, result); } }; var procedure = function(type, handler, result) { var func = handler[type]; var def = handler.deferred; if (func) { try { var newResult = func(result); if (newResult && typeof newResult.then === ' function ') {//thenable // 此种写法存在闭包容易造成内存泄露,我们通过高阶函数解决 // newResult.then(function(data) { // def.resolve(data); // }, function(err) { // def.reject(err); // }); //PromiseA+规范,x代表newResult,promise代表def.promise //If x is a promise, adopt its state [3.4]: //If x is pending, promise must remain pending until x is fulfilled or rejected. //If/when x is fulfilled, fulfill promise with the same value. //If/when x is rejected, reject promise with the same reason. newResult.then(makeSignaler(def, ' fulfilled '), makeSignaler(def, ' rejected '));//此处的本质是利用了异步闭包 } else { transition(def, type, newResult); } } catch(err) { transition(def, ' rejected ', err); } } else { transition(def, type, result); } }; var transition = function(deferred, type, result) { if (type === ' fulfilled ') { deferred.resolve(result); } else if (type === ' rejected ') { deferred.reject(result); } else if (type !== ' pending ') { throw new Error({ ' message ': "doesn' t support type: " + type }); } }; return { 'procedure' : procedure } })(); Deferred = function () { this .promise = new Promise(); }; Deferred.prototype.resolve = function (result) { if (! this .promise.isPending()) { return ; } var queue = this .promise.getQueue(); for ( var i = 0, len = queue.length; i < len; i++) { utils.procedure( 'fulfilled' , queue[i], result); } this .promise.setStatus( 'fulfilled' , result); }; Deferred.prototype.reject = function (err) { if (! this .promise.isPending()) { return ; } var queue = this .promise.getQueue(); for ( var i = 0, len = queue.length; i < len; i++) { utils.procedure( 'rejected' , queue[i], err); } this .promise.setStatus( 'rejected' , err); } |
通过Promise机制我们的编程方式可以变成这样:
request = function (url) { var def = new Deferred(); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) { def.resolve(xhr.responseText) } else { //简化ajax,没有提供错误回调 def.reject( new Error({ message: xhr.status })); } } }; xhr.open( 'get' , url, true ); xhr.send( null ); return def.promise; } request( 'data1.json' ).then( function (data1) { console.log(data1); //处理data1 return request( 'data2.json' ); }).then( function (data2) { console.log(data2); //处理data2 return request( 'data3.json' ); }, function (err) { console.error(err); }).then( function (data3) { console.log(data3); alert( 'success' ); }, function (err) { console.error(err); }); |
对于并行逻辑串行执行问题我们可以这样解决
//所有异步操作都完成时,进入完成态, //其中一项异步操作失败则进入失败态 all = function (requestArray) { // var some = Array.prototype.some; var def = new Deferred(); var results = []; var total = 0; requestArray.some( function (r, idx) { //为数组中每一项注册回调函数 r.then( function (data) { if (def.promise.isPending()) { total++; results[idx] = data; if (total === requestArray.length) { def.resolve(results); } } }, function (err) { def.reject(err); }); //如果不是等待状态则停止,比如requestArray[0]失败的话,剩下数组则不用继续注册 return !def.promise.isPending(); }); return def.promise; } all( [request( 'data1.json' ), request( 'data2.json' ), request( 'data3.json' )] ).then( function (results){ console.log(results); // 处理data1,data2,data3 alert( 'success' ); }, function (err) { console.error(err); }); |
以下是几个测试案例
//链式调用 var p1 = new Deferred(); p1.promise.then( function (result) { console.log( 'resolve: ' , result); return result; }, function (err) { console.log( 'reject: ' , err); return err; }).then( function (result) { console.log( 'resolve2: ' , result); return result; }, function (err) { console.log( 'reject2: ' , err); return err; }).then( function (result) { console.log( 'resolve3: ' , result); return result; }, function (err) { console.log( 'reject3: ' , err); return err; }); p1.resolve( 'success' ); //p1.reject('failed'); p1.promise.then( function (result) { console.log( 'after resolve: ' , result); return result; }, function (err) { console.log( 'after reject: ' , err); return err; }).then( function (result) { console.log( 'after resolve2: ' , result); return result; }, function (err) { console.log( 'after reject2: ' , err); return err; }).then( function (result) { console.log( 'after resolve2: ' , result); return result; }, function (err) { console.log( 'after reject2: ' , err); return err; }); //串行异步 var p2 = new Deferred(); p2.promise.then( function (result) { var def = new Deferred(); setTimeout( function (){ console.log( 'resolve: ' , result); def.resolve(result); }) return def.promise; }, function (err) { console.log( 'reject: ' , err); return err; }).then( function (result) { var def = new Deferred(); setTimeout( function (){ console.log( 'resolve2: ' , result); def.reject(result); }) return def.promise; }, function (err) { console.log( 'reject2: ' , err); return err; }).then( function (result) { console.log( 'resolve3: ' , result); return result; }, function (err) { console.log( 'reject3: ' , err); return err; }); p2.resolve( 'success' ); //并行异步 var p1 = function (){ var def = new Deferred(); setTimeout( function () { console.log( 'p1 success' ); def.resolve( 'p1 success' ); }, 20); return def.promise; } var p2 = function (){ var def = new Deferred(); setTimeout( function () { console.log( 'p2 failed' ); def.reject( 'p2 failed' ); }, 10); return def.promise; } var p3 = function (){ var def = new Deferred(); setTimeout( function () { console.log( 'p3 success' ); def.resolve( 'p3 success' ); }, 15); return def.promise; } all([p1(), p2(), p3()]).then( function (results) { console.log(results); }, function (err) { console.error(err); }); |
Promise优点
对比使用Promise前后我们可以发现,传统异步编程通过嵌套回调函数的方式,等待异步操作结束后再执行下一步操作。过多的嵌套导致意大利面条式的代码,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
参考文章:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步