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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,560评论 1 56
  • Promise 的含义 一句话概括一下promise的作用:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套...
    雪萌萌萌阅读 5,325评论 0 7
  • 目录:Promise 的含义基本用法Promise.prototype.then()Promise.prototy...
    BluesCurry阅读 1,470评论 0 8
  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 797评论 0 2
  • 网络效应 网络效应指的是某种产品对一名用户的价值,取决于使用这个产品的其他用户的数量。用户越多,越有价值。 一个软...
    haominzeng阅读 534评论 0 2