异步Promise实现
简介
异步回调的书写往往打乱了正常流的书写方式,在ECMAScript 6中实现了标准的Promise API,旨在
解决控制回调流程的问题。
简单的实现了Promise API:
1 (function(w){ 2 function Promise(fn){ 3 return this instanceof Promise ? this.init(fn) : new Promise(fn); 4 } 5 Promise.fulfill = function(m){return m;}; 6 Promise.reject = function(m){throw m;}; 7 Promise.map = { 8 resolve: "onFulfill", 9 reject: "onReject" 10 } 11 //异步自动生成promise并执行 12 Promise.resolve = function(fn){ 13 var p = new Promise(); 14 setTimeout(function(){ 15 p.resolve(); 16 },0); 17 if(fn) 18 p.callback["onFulfill"] = fn; 19 return p; 20 }; 21 Promise.all = function(){ 22 var p = new Promise(), 23 args; 24 var counter = 0,ret = [];//收集结果,并传给p 25 var v,fn; //传入的函数,执行该函数,将结果保存至ret 26 if(arguments.length > 1){ 27 args = [].slice.apply(arguments) 28 }else if({}.toString.call(arguments[0]) == "[object Array]"){ 29 args = arguments[0]; 30 } 31 for(var i=0,len=args.length;i<len;i++){ 32 if(typeof args[i] == "function"){ 33 args[i] = Promise.resolve(args[i]); 34 } 35 36 (function(i){ 37 args[i].then(function(m){ 38 ret.push(m); 39 if(--counter <= 0){ 40 ret.length = len; 41 p.resolve(ret); 42 } 43 },function(){ 44 p.reject(); 45 }); 46 })(i) 47 counter++; 48 } 49 return p; 50 }; 51 Promise.prototype = { 52 init: function(fn){ 53 var that = this; 54 this.state = 'pending'; 55 this.callback = { 56 onFulfill: Promise.fulfill, 57 onReject: Promise.reject 58 }; 59 this.dirty = false; 60 this._next = null; 61 setTimeout(function(){ 62 fn && fn.call(that,that.resolve.bind(that),that.reject.bind(that)); 63 },0) 64 65 }, 66 then: function(onFulfill,onReject){ 67 return post.call(this,onFulfill,onReject); 68 }, 69 wait: function(mills){ //promise链在wait处被分裂成2段 70 var p = new Promise(), 71 start = new Date().getTime(); 72 var id = setTimeout(function(){ //传入时间 73 p.resolve([this.val,new Date().getTime() - start]) 74 },mills); 75 p.cancel = function(){ 76 clearTimeout(id); 77 } 78 return p; 79 } 80 } 81 function post(onFulfill,onReject,onNotify,onComplete){ 82 var p = new Promise(), 83 that = this; 84 if(arguments.length <= 2){ 85 that._next = p; 86 that.callback["onFulfill"] = onFulfill; 87 that.callback["onReject"] = onReject; 88 this.dirty = true; 89 } 90 return p; 91 } 92 function fire(promise,method){ 93 var next = "resolve",val, 94 args = arguments[2]; 95 if(promise.state == "pending"){ 96 try{ 97 promise.val = val = promise.callback[Promise.map[method]].apply(promise,args); 98 promise.state = method; 99 }catch(e){ 100 promise.val = val = e; 101 next = "reject"; 102 } 103 104 if(val && isPromise(val)){ 105 val._next = promise._next; 106 }else{ 107 if(promise._next){ 108 fire(promise._next,next,[val]); 109 } 110 } 111 112 } 113 return promise; 114 } 115 function isPromise(o){ 116 return o && typeof o == "object" && o.then && typeof o.then == "function"; 117 } 118 "reject,resolve".replace(/\w+/g,function(m){ 119 Promise.prototype[m] = function(){ 120 return fire(this,m,arguments); 121 } 122 }) 123 124 w.Promise = Promise; 125 })(window)
示例
示例内容为依次加载网页内容的各个元素:先加载标题,并根据服务器返回的url信息,到相应的文件中加载
内容,并以此显示。
1 var getJson = function(url){ 2 return new Promise(function(resolve,reject){ 3 var that = this; 4 var xhr = new XMLHttpRequest(); 5 if(!window.Promise)return; 6 xhr.open('get',url); 7 xhr.onreadystatechange = function(e){ 8 if(xhr.readyState == 4){ 9 if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 10 resolve(xhr.responseText); log(that) 11 }else{ 12 reject(new Error('response error')); 13 } 14 } 15 }; 16 xhr.onerror = function(e){ 17 reject(new Error('ajax error')); 18 } 19 xhr.send(); 20 }); 21 }; 22 23 var body = document.body; 24 var addHtml = function(html){ 25 if(typeof html != 'string') return; 26 var p = document.createElement('p'); 27 p.textContent = html; 28 body.insertBefore(p,loading); 29 }; 30 var addHead = function(html){ 31 if(typeof html !== 'string') return; 32 var h = document.createElement('h2'); 33 h.textContent = html; 34 body.insertBefore(h,loading); 35 } 36 var log = function(msg){console.log(msg)}; 37 var loading = document.getElementById('loading'); 38 39 getJson('../json/head.json').then(JSON.parse).then(function(html){ 40 addHead(html.content); 41 Promise.all(html.urls.map(getJson)).then(function(arr){ 42 arr.forEach(function(content){ 43 addHtml(JSON.parse(content).content); 44 }) 45 },function(e){ 46 log('error in loading content: '+ e); 47 }) 48 },function(e){ 49 log('error: ' + e); 50 }).then(function(){ 51 loading.style.display = 'none'; 52 }) 53 54 getJson('../json/head.json').then(JSON.parse).then(function(html){ 55 addHead(html.content); 56 var promise = Promise.resolve(); 57 html.urls.forEach(function(url,i){ 58 promise = promise.then(function(){ 59 return getJson(url); 60 }).then(JSON.parse).then(function(html){ 61 addHtml(html.content); 62 },function(e){ 63 log('error in loading body: '+ e ); 64 }).then(function(){ 65 if(i == html.urls.length-1) 66 loading.style.display = 'none'; 67 }) 68 }) 69 })
示范
Promise API控制流程,尤其是对于异步操作而言,流程非常清晰,开飞相对容易。