學習promise編寫和使用

promise實現

Promise是Javascript中的一種異步編程實現方式,js中異步編程主要是指瀏覽器DOM事件處理,setTimeout/setInterval,ajax等,經過傳入回調函數來實現控制反轉。Promsie對象符合CommonJS編程規範,目的是爲異步編程提供統一接口,它最大的優勢就是避免了回調金字塔。
假設要實現一個用戶展現的任務,任務分爲三步:html

  1. 獲取用戶信息jquery

  2. 獲取用戶圖片ajax

  3. 彈窗提示
    通常以前使用方式爲:編程

以前已經對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的回調函數就是getUserImageapp

getUserInfo函數要進行以下改寫(這裏用jQuery的Deferred()實現)框架

function getUserInfo(id){
    var dfd = $.Deferred();
    setTimeout(function(){
        //獲取用戶信息
        dfd.resolve();
    }, 500);

    return dfd.promise;
}

這樣一種寫法回調函數就成了鏈式寫法,程序的流程很是清楚,能夠實現多個回調。
先在ES6出來以後,許多瀏覽器已經支持Promise方法,
這裏寫圖片描述
promise有三種狀態:異步

  1. pending:初始狀態異步編程

  2. fulfilled:成功操做

  3. rejected:失敗操做
    開始的時候promise是pending狀態,fulfill以後就會執行回調then的回調函數,若是是reject就會調用catch進行異常處理,Promise.prototype.then() Promise.prototype.catch() 兩種方法狀態都會返回一個promise對象,用於後續鏈式調用。

創建一個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()方法

這裏寫圖片描述

實現XMLHttpRequest獲取數據

使用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

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對象詳解

Promise MDN

相關文章
相關標籤/搜索