Promise对象和Deferred对象

Promise对象

Promise是异步编程的一种解决方案,就像一个容器,里面装着未来才会结束的事件的结果,一般是一个异步操作。
Promise的特点:
(1)Promise对象有三种状态pending进行中,fulfilled已成功,rejected已失败。异步操作的结果决定当前的状态是哪一种,其他操作不会改变这个状态。
(2)Promise的状态改变只有两种可能,即从pending变为fulfilled,从fulfilled变为rejected,一旦状态改变就会一直保持这个结果,这时就是resolved已定型
Promise一旦新建就会立即执行,无法中途取消,成功或者失败的信息都会体现在回调函数里面,若不设置回调函数,则这些信息不会反应到对象外部
Promise的用法:
根据ES6的规定,Promise对象是一个构造函数,可以生成Promise实例,构造函数接受一个回调函数,该回调函数的参数有两个,resolvereject,均由JS引擎提供,不用自己部署;
生成实例后,可以用then方法分别指定resolvedrejected状态的回调函数(这里用链式操作的方式)。

var promise = new Promise( function( resolve, reject ){
  if(/*异步操作成功*/){
    resolve( result );
  } else {
    reject( error );
  }
} ).then( function( result ) {
    // success
}, function( error ) {
    // failure
});
let promise = new Promise( ( function( resolve, reject ) {
    console.log('Promise');
    resolve();
} ) ).then( function( result ) {
    console.log( 'resolve' );
} );
console.log( 'hello promise' );

以上输出顺序是 Promise, hello promise, resolve
Promise新建后就会立即执行,因此先输出Promise,then回调函数里面的任务是异步执行的,要等到同步任务执行完才能执行,因此后面依次输出hello promise, resolve。 异步加载图片,成功就调用resolve方法,失败就调用reject

function loadImgAsync( url ) {
    return new Promise( function ( resolve, reject ) {
        const img = new Image;
        img.onload = function () {
            resolve( img );
        };
        img.onerror = function () {
            reject( new Error(`Could not load image at ${url}`) );
        };
        img.src = url;
    } );
}

Promise对象实现的 Ajax 操作,若调用resolve和reject时带参数,则这些参数会被传递给回调函数

const getJSON = function (url) {

    const promise = new Promise(function (resolve, reject) {
        const hanlder = function () {
            // 此处的this就是client
            console.log(this);
            if (this.reayState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };

        var client;
                if (window.XMLHttpRequest) {
            //code for IE7+, Firefox, Chrome, Opera, Safari
            client = new XMLHttpRequest();
        } else {
            //code for IE5, IE6
            client = new ActiveXObject("Microsoft.XMLHTTP");

        }
        client.open('get', url);
        client.onreadystatechange = hanlder;
        client.responseType = 'json';
        client.send(null);
    });
    return promise;
};

getJSON('/banner.json').then(function (result) {
    console.log(result);
}, function (error) {
    console.log('阿哦出错辽');
});

resolve的参数可以是一个Promise实例

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

此时p1的状态决定了p2的状态,若p1的状态是pending,则p2的回调函数就需要等待p1的状态改变,若p1的状态是resolvereject,则p2的回调函数会立即执行。若Promise 状态已经变成resolved,再抛出错误是无效的

Promise常用的方法
Promise.prototype.catch() 相当于.then(null, rejection).then(undefined, rejection) 指定发生错误时的回调函数,一般在then方法里面处理成功的回调函数,catch里面处理失败的回调函数

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

promise.then(fnuction(result){
  // do something
}).catch(function(err){
  // throw error
});

Promise.prototype.finally()
不管Promise对象最后状态如何,finally里面的代码都会执行
ES2018引入标准

promise.finally(() => {
  // 语句
});

// 等同于
promise.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

Promise.prototype.finally()实现

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

Promise.all():将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);

只有p1, p2, p3状态都为fulfilled,p的状态才是fulfilled,但只要有一个rejected,p的状态就会是rejected

// 只有`booksPromise`, `userPromise`这两个异步操作的结果都返回了,
// 才会触发`pickTopRecommendations`这个回调函数
const databasePromise = connectDatabase();
const booksPromise = databasePromise.then(findAllBooks);
const userPromise = databasePromise.then(getCurrentUser);
Promise.all([
  booksPromise,
  userPromise
]).then(([books, user]) => pickTopRecommendations(books, user));

如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

// p2指定自己的catch方法
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
}).then(result => result).catch(e => e);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
}).then(result => result).catch(e => e);
Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
// ["hello", Error: 报错了]

// p2没有自己的catch方法
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
}).then(result => result);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
}).then(result => result);

Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
// Error: 报错了

Promise.race():将多个 Promise 实例,包装成一个新的 Promise 实例,与Promise.all()不同的是,只要p1,p2,p3中的任意一个先改变状态,p的状态就会跟着改变,先改变的Promise实例的返回值传递给p的回调函数

Promise.resolve():将现有对象转为 Promise 对象,该对象的状态为resolved,接收的参数分为四种情况
(1)参数是 Promise 实例:不做修改,原样返回
(2) thenable 对象:有then方法的对象,先转为Promise对象再立即执行这个then方法
(3)参数不是具有then方法的对象,或根本就不是对象:返回一个新的 Promise 对象,状态为resolved
(4)无参数:直接返回一个resolved状态的 Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.reject():同Promise.resolve(),但转之后的 Promise 对象状态为rejected,接收的参数与Promise.resolve()不同,会直接变为后续方法的参数

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable).catch(e => {
  console.log(e === thenable)
})
// true
// 接收的不是reject抛出的字符串,而是整个thenable对象

Promise.try():模拟try代码块

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}
// 等同于
Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

Deferred对象

deferred对象是jQuery用来统一处理回调函数的一个解决方案。主要功能归结为4点
(1)ajax链式写法
$ajax()操作完成返回一个deferred对象,可以进行链式操作

$ajax('xxx')
.done(function(){
  // success
})
.fail(function(){
  // failure
});

done()相当于success方法,fail()相当于error方法
(2)指定同一个操作的多个回调函数
deferred对象允许自由添加多个回调函数。

// 上面的实例代码完成后还想继续加回调函数,按顺序执行
$ajax('xxx')
.done(function(){
  // success
})
.fail(function(){
  // failure
});
.done(function(){
  //   第二个回调函数
});

(3)deferred对象允许为多个事件添加同一个回调函数,使用 $when()

$.when($.ajax('xxx'), $ajax('yyy'))
.done(function(){
  // success
})
.fail(function(){
  // failure
});

若两个ajax操作都成功就执行done回调函数,若有一个失败就执行fail回调函数
(4)提供回调函数的接口,任何操作都可以使用deferred对象的各种方法(包括异步同步),指定回调函数,$.when()的参数只能是deferred对象

// 假设一个很耗时的操作wait
var wait = function(){
  var task = function(){
    alert('success');
  };
  setTimeout(task, 5000);
}

// 用$.when()改写  wait()函数运行完,就会自动运行done()方法指定的回调函数
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(){
  var task = function(){
    alert('success');
    dtd.resolve();  //  改变deferred对象的执行状态
  };
  setTimeout(task, 5000);
  return dtd;
}
$.when(wait(dtd))
.done(function(){ alert("success"); })
.fail(function(){ alert("failure"); });

(5)deferred.resolve()方法和deferred.reject()方法
jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数
类似于Promise的三种状态
(6)deferred.promise()方法

var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(){
  var task = function(){
    alert('success');
    dtd.reject();  //  改变deferred对象的执行状态
  };
  setTimeout(task, 5000);
  return dtd;
}
$.when(wait(dtd))
.done(function(){ alert("success"); })
.fail(function(){ alert("failure"); });
dtd.reject(); 

上面的代码会先弹出success,5秒之后弹出failure,这是因为最下面改变了dtd的状态为已完成,因此会立即触发done回调函数。
这种可以随意在方法外部改变deferred对象状态的操作可以用deferred.promise()防止。即在原来的对象上返回另一个deferred对象,返回的对象只开放与改变状态无关的方法(donefail),屏蔽与改变执行状态有关的方法(resolvereject),使执行状态不能被改变。

var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
    var tasks = function(){
    alert("complete");
   dtd.resolve(); // 改变Deferred对象的执行状态
 };
 setTimeout(tasks,5000);
 return dtd.promise(); // 返回promise对象
};
var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
$.when(d)
 .done(function(){ alert("success"); })
 .fail(function(){ alert("failure"); });
d.resolve(); // 此时,这个语句是无效的
// 若要改变执行状态,只能操作原来的deferred对象

// 改进版,将dtd对象变为函数的内部对象
var wait = function(){
  var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
  var tasks = function(){
      alert("complete");
      dtd.resolve(); // 改变Deferred对象的执行状态
  };
  setTimeout(tasks,5000);
  return dtd.promise(); // 返回promise对象
};
$.when(wait())
 .done(function(){ alert("success"); })
 .fail(function(){ alert("failure"); });

另外一种防止外部改变执行状态的方法,使用deferred对象的构造函数。$.Deferred()可以接受一个函数名作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数

$.Deferred(wait)
 .done(function(){ alert("success"); })
 .fail(function(){ alert("failure"); });

第三种方法:直接在wait对象上部署deferred接口

var dtd = $.Deferred(); // 生成Deferred对象
var wait = function(dtd){
    var tasks = function(){
        alert("success");
        dtd.resolve(); // 改变Deferred对象的执行状态
    };
    setTimeout(tasks,5000);
  };
dtd.promise(wait);  // 关键一句
wait.done(function(){ alert("success"); })
    .fail(function(){ alert("failure"); });
wait(dtd);

always() 不管状态如何都会执行

参考链接http://es6.ruanyifeng.com/#docs/promise
http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

你可能感兴趣的:(Promise对象和Deferred对象)