清姿
IN A GOOD WAY

Promise定义

Promise是CommonJs的规范之一,包含resolve,reject,done,fail,then等方法,能够帮助我们控制代码的流程,避免函数的多层嵌套。异步在web开发中越来越重要,对于开发人员来说,J非线性执行的编程会让开发者觉得难以掌控,而Promise可以让我们更好地掌控代码的执行流程,jQuery等流行的js库都已经实现了这个对象,现在ES6已经原生实现了Promise。

场景说明:

有时候会遇到这样的情况,需要发两个异步请求,而第二个请求需要用到第一个返回结果的数据:

ajax({
        url: url1,
        success: function(data) {
            ajax({
                url: url2,
                data: data,
                success: function() {
                }
            });
        }
});

稍微整理一下,把后一个调用封装成一个函数,就是酱紫滴:

    function A() {
        ajax({
            url: url1,
            success: function(data) {
                B(data);
            }
        });
    }
    function B(data) {
        ajax({
            url: url2,
            success: function(data) {
                ......
            }
        });
    }

如果异步请求C又需要用到B的结果,又需要一层。如果需要很多层,代码可读性会降低,最后不忍直视了就。所以就有了Promise。有了Promise,代码就是酱紫了:

new Promise(A).done(B);

很清爽很DRY有木有?

ES6已经实现了Promise,不过也可以自己写一个Promise对象:

用两个数组(doneList和failList)来存储成功时的回调函数队列和失败时的回调函数队列。

此对象的方法和属性:

1.state: 当前执行状态,有pending、resolved、rejected3种取值

2.done: 向doneList中添加一个成功回调函数

3.then: 分别向doneList和failList中添加回调函数

4.always: 添加一个无论成功还是失败都会调用的回调函数

5.resolve: 将状态更改为resolved,并触发绑定的所有成功的回调函数

6.reject: 将状态更改为rejected,并触发绑定的所有失败的回调函数

7.when: 参数是多个异步或者延迟函数,返回值是一个Promise兑现,当所有函数都执行成功的时候执行该对象的resolve方法,反之执行该对象的reject方法

具体实现:

var Promise = function() {
    this.doneList = [];
    this.failList = [];
    this.state = 'pending';
};

Promise.prototype = {
    constructor: 'Promise',
    resolve: function() {
        this.state = 'resolved';
        var list = this.doneList;
        for(var i = 0, len = list.length; i < len; i++) {
            list[0].call(this);
            list.shift();
        }
    },
    reject: function() {
        this.state = 'rejected';
        var list = this.failList;
        for(var i = 0, len = list.length; i < len; i++){
            list[0].call(this);
            list.shift();
        }
    },
    done: function(func) {
        if(typeof func === 'function') {
            this.doneList.push(func);
        }
        return this;
    },
    fail: function(func) {
        if(typeof func === 'function') {
            this.failList.push(func);
        }
        return this;
    },
    then: function(doneFn, failFn) {
        this.done(doneFn).fail(failFn);
        return this;
    },
    always: function(fn) {
        this.done(fn).fail(fn);
        return this;
    }
};

这个不支持链式调用,可以再来个支持链式调用的:

var MPromise = function(func){
    this.doneList = [];
    this.state = 'pending';
    func(this.resolve.bind(this));
    this.self = this;
}
MPromise.prototype = {
    resolve: function(){
        var args = arguments[0];

        while(true){
            args = [args];
            this.lastargs = args;

            if( this.doneList.length == 0 ){
                this.state = 'done';
                break;
            }

            args = this.doneList.shift().apply(this, args);

            if( args instanceof MPromise ){
                this.self = args;
                args.doneList = args.doneList.concat(this.doneList.slice(0));
                this.state = 'done';
                this.doneList.length = 0;
                break;                        
            }
        }
    },
    then: function(callback){
        this.doneList.push(callback);
        if( this.state == 'done' ){
            this.state = 'pending';
            this.resolve.apply(this, this.lastargs);
        }
        return this.self;
    }
}

ES6-Promise

ES6提供了原生Promise对象:从语法上说,Promise是一个对象,从中可以获取异步操作的消息。

基本用法:

ES6中的Promise对象是一个构造函数,用来生成Promise实例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise对象接收一个函数作为参数,这个函数的两个参数分别是resolve和reject,这两个函数由Javascript引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

 一个Promise的简单例子:

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});

timeout返回了一个Promise实例,延迟一段时间再执行,超过指定时间之后,Promise实例的状态会变成Resolved,会触发then方法绑定的回调函数。

Promise新建后会立即执行:

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('Resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// Resolved

如上,Promise新建之后立即执行,所以首先输出Promise,然后then方法指定的回调函数,是在当前所有脚本执行之后才执行,所以Resolved最后输出。

一个异步加载图片的例子:

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}

这里使用Promise封装了一个图片懒加载,加载成功则调用resolve方法,加载失败会调用rejected方法。

下面是一个用Promise实现Ajax调用的栗子:

var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

    function handler() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('Error', error);
});

上面getJSON是对XMLHttpRequest的封装,用来发一个对JSON数据的Http请求,并且返回一个Promise对象。在getJSON内部,resolve和reject函数调用时,都带有参数。

如果调用resolve函数和reject函数时带有参数,那么参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。

var p1 = new Promise(function (resolve, reject) {
  // ...
});

var p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

上面代码里面的p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作结果是另外一个异步操作。

这里p1的状态就会传到p2,也就是,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态的改变;如果p1的状态是resolved或rejected,那么p2的回调函数会立即执行。

var p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
p2.catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个Promise,3秒之后变为rejected。p2的状态由p1决定,1秒之后,p2调用resolve方法,但是此时p1的状态还没有改变,因此p2的状态也不会变。又过了2秒,p1变为rejected,p2也跟着变为rejected。

参考:http://es6.ruanyifeng.com/#docs/promise

posted on 2016-06-16 19:45  清姿  阅读(717)  评论(0编辑  收藏  举报