Promise是Javascript中的一種異步編程實現方式,js中異步編程主要是指瀏覽器DOM事件處理,setTimeout/setInterval
,ajax等,經過傳入回調函數來實現控制反轉。Promsie對象符合CommonJS編程規範,目的是爲異步編程提供統一接口,它最大的優勢就是避免了回調金字塔。
假設要實現一個用戶展現的任務,任務分爲三步:html
獲取用戶信息jquery
獲取用戶圖片ajax
彈窗提示
通常以前使用方式爲:編程
以前已經對
getUserInfo()
和getUserImage()
,showTip()
方法進行了定義promise
getUserInfo(id, function(info){ getUserImage(info.img, function(){ showTip(); }) })
若是回調函數不止3個,那將會是一個很是長的回調,換成Promise實現:瀏覽器
//getUserInfo返回promise getUserInfo(id) .then(getUserImage) .then(showTip) .catch(function(e){ console.log(e); });
簡單的講,Promsie的思想就是每個異步任務返回一個promise對象,該對象有一個then方法,容許指定回調函數。這裏getUserInfo
的回調函數就是getUserImage
。app
getUserInfo函數要進行以下改寫(這裏用jQuery的Deferred()實現)框架
function getUserInfo(id){ var dfd = $.Deferred(); setTimeout(function(){ //獲取用戶信息 dfd.resolve(); }, 500); return dfd.promise; }
這樣一種寫法回調函數就成了鏈式寫法,程序的流程很是清楚,能夠實現多個回調。
先在ES6出來以後,許多瀏覽器已經支持Promise方法,
promise有三種狀態:異步
pending:初始狀態異步編程
fulfilled:成功操做
rejected:失敗操做
開始的時候promise是pending狀態,fulfill以後就會執行回調then的回調函數,若是是reject就會調用catch進行異常處理,Promise.prototype.then()
和 Promise.prototype.catch()
兩種方法狀態都會返回一個promise對象,用於後續鏈式調用。
<button id="btn">Make a promise!</button> <div id="log"></div>
'use strict'; var promiseCount = 0; function testPromise(){ var thisPromiseCount = promiseCount++; var log = document.getElementById('log'); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 開始 (<small>異步調用開始</small>)<br/>'); //新建一個promsie對象 var p1 = new Promise(function(resolve, reject){ log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Promise 開始執行 (<small>異步調用開始</small>)<br/>'); window.setTimeout(function(){ resolve(thisPromiseCount); }, 2000) }); //定義promise執行fulfilled執行的then()回調函數以及執行reject後的catch回調函數 p1.then( function(val) { log.insertAdjacentHTML('beforeend', val + ') Promise fulfilled (<small>異步代碼中斷</small>)<br/>'); }) .catch( function(reason) { console.log('Handle rejected promise ('+reason+') here.'); }); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 創建promise'); }if ("Promise" in window) { var btn = document.getElementById("btn"); btn.addEventListener("click",testPromise); } else { log = document.getElementById('log'); log.innerHTML = "瀏覽器不支持promise接口"; } }
單擊按鈕後,首先執行promise
3秒以後resolve調用then()方法
使用ajax異步加載圖片
function imgLoad(url) { //返回promsie對象 return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onload = function() { if (xhr.status === 200) { resolve(xhr.response); } else { reject(Error('圖像加載失敗; 錯誤緣由:' + xhr.statusText)); } }; xhr.onerror = function() { reject(Error('加載錯誤.')); }; xhr.send(); }); } var body = document.querySelector('body'); var myImage = new Image(); var url = 'http://7qnb7a.com1.z0.glb.clouddn.com/6608717992840918931.jpg'; imgLoad(url).then(function(response) { var imageURL = window.URL.createObjectURL(response); myImage.src = imageURL; body.appendChild(myImage); }, function(Error) { console.log(Error); });
首先須要創建一個對象來存儲promise
Promise = function(){ this.queue = []; this.value = null; this.status = 'pending'; };
定義獲取隊列,設定狀態和獲取狀態原型方法
Promsie.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); }else{ throw new Error({message:"doesn't support status: "+s}); } };
定義then方法,接受兩個參數用於完成和拒絕任務操做。
Promise.prototype.then = function(onFulfilled, onRejected){ var handler = { 'fulfilled': onFulfilled, 'rejected': onRejected }; handler.deferred = new Deferred(); if(!this.isPending()){ utils.procedure(this.status, handler, this.value); }else{ this.queue.push(handler); } return handler.deferred.promise; };
定義三個不一樣狀態函數
Promise.prototype.isFulfilled = function(){ return this.status === 'fulfilled'; }; Promise.prototype.isRejected = function(){ return this.status === 'rejected'; }; Promise.prototype.isPending = function(){ return this.status === 'pending'; };
工具處理函數
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'){ 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({'messgae':"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); }
有不少實現了promises的庫供開發者可用。 像jQuery的 Deferred, 微軟的 WinJS.Promise, when.js, q, 和dojo.Deferred.
關於jQuery的Deferred能夠參考jQuery的deferred對象詳解