ES6-Promise对象 (上)

前言

在Promise之前,js的异步编程都是采用回调函数和事件的方式。但是这种编程方式在处理复杂业务的情况下,很容易出现callback hell(回调地狱),使得代码很难被理解和维护。Promise就是改善这种情形的异步编程的解决方案,它由社区最早提出和实现,es6将其写进了语言标准,统一了用法,并且提供了一个原生的对象Promise。

但是在这之前,大家想要使用Promise,一般会借助于第三方库,或者当你知道其中的原理以后,也可以手动实现一个简易的Promise.当然,为了防止不可预知的bug,在生产项目中最好还是不要使用原生的或者自己编写的Promise(目前为止并不是所有浏览器都能很好的兼容ES6),而是使用已经较为成熟的有大量小伙伴使用的第三方Promise库。现今流行的各大js库,几乎都不同程度的实现了Promise,如jQuery、Zepto等,只是暴露出来的大都是Deferred对象,当然还有angularJs中的$q。

注:以下所有的测试代码请在高级浏览器或node环境下运行
我们还是先来看看Promise的真身

console.log(new Promise(
    function(resolve,reject){ })
);

打开浏览器的控制台我们可以看到:

Promise.png

从控制台输出的Promise对象我们可以清楚的看到Promise对象有以下几种基本方法:
Promise.resolve()
Promise.reject()
Promise.all()
Promise.race()
Promise.prototype.then()
Promise.prototype.catch()
更多点击Promise官网API,我们先不着急记住他们,只要对Promise对象有个整体的认识。


1.Promise对象状态

  • pending: 初始状态, 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.
    pending状态的Promise对象既可转换为带着一个成功值的fulfilled状态,也可变为带着一个失败信息的 rejected状态。当状态发生转换时,Promise.then绑定的方法就会被调用。(当绑定方法时,如果 Promise对象已经处于fulfilled或rejected状态,那么相应的方法将会被立刻调用,所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。)
    因为Promise.prototype.then和Promise.prototype.catch方法返回Promises对象, 所以它们可以被链式调用。
Promise对象活动流程.png

注意,Promise状态的改变只会出现从未完成态向完成态或失败态转化,不能逆反。完成态和失败态不能互相转化,而且,状态一旦转化,将不能更改。


2.基本用法

(1)constructor

语法

new Promise(executor);
new Promise(function(resolve, reject) { ... });

参数

name desc
executor 带有resolve、reject两个参数的函数对象。第一个参数用在处理执行成功的场景,第二个参数则用在处理执行失败的场景。一旦我们的操作完成即可调用这些函数。

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

var promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

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

上面代码中,Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从“未完成”变为“成功”(即从 pending 变为 resolved);如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从“未完成”变为“失败”(即从 pending 变为 rejected)。
Promise 实例生成以后,可以用 then 方法分别指定 resolve 方法和 reject 方法的回调函数。

这里我们要特别注意两点:
1⃣️ Promise 新建后就会立即执行。
我们来一段代码测试一下:

var p1=new Promise(function(res,rej){
    setTimeout(()=>{res("p1 end!");},1200);
    console.log('我先执行');
})

p1.then(function(data){
    console.log(data);
})

控制台输出:
我先执行
p1 end!

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

var p1 = new Promise(function(resolve, reject){
  // ...
});
var p2 = new Promise(function(resolve, reject){
  // ...
  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);
  console.log('p1p1p1');
})

var p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
  console.log('p2p2p2');
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error));

执行结果如下:


res.png

这里也再次说明第一个注意点提到的Promise 新建后就会立即执行;

(2)Promise.prototype.then()

在实例化一个Promise对象之后,我们调用该对象实例的then()方法为实例添加状态改变时的回调函数:

  • 第一个参数(函数)是resolved状态的回调函数
  • 第二个参数(函数)是rejected状态的回调函数

我们做一个异步加载图片实际案例来小试牛刀

function loadImageAsync(url) {
    return new Promise(function (reslove, reject) {
        var img = new Image();
        img.onload = function () {
            reslove();
        }
        img.onerror = function () {
            reject();
        }
        console.log("loading image");
        img.src = url;
    });
}
var loadImage1 = loadImageAsync("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1510427875428&di=0df1bd50978bba9e0c0ba75e325b956a&imgtype=0&src=http%3A%2F%2Fimg.zybus.com%2Fuploads%2Fallimg%2F141110%2F1-141110163F6.jpg"); //来自百度的梅西图片
loadImage1.then(function success() {
    console.log("loadImage1 load success");
}, function fail() {
    console.log("loadImage1 load fail");
});

var loadImage2 = loadImageAsync("http://127.0.0.1/upload/patty.png");  //这张图片不存在
loadImage2.then(function success() {
    console.log("loadImage2 load success");
}, function fail() {
    console.log("loadImage2 load fail");
});

我们看看控制台发生了什么:


loadPhoto.png
返回值

Promise.prototype.then 方法返回的是一个新的Promise对象,因此可以采用链式写法,即then方法后面再调用另一个then方法。

then方法返回的新的Promise对象的行为与then中的回调函数的返回值有关:

  • 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  • 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  • 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

是不是看着有一种迷糊的感觉,我们这里只看第一种情况。

var p2 = new Promise(function(resolve, reject) {
  resolve(1);
});

p2.then(function(value) {
  console.log(value);   //1
  return value + 1;      //then中的回调函数返回一个值
}).then(function(value) {
  console.log(value); // 2
});

这里有一点我们必须了解:
如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。

(3)Promise.prototype.catch()

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定发生错误时的回调函数。这一点官方api中说的很清楚。

catch.png

catch 方法可以用于您的promise组合中的错误处理,这个方法其实很简单,在这里并不想讨论它的使用,而是想讨论的是Promise中的错误的捕抓和处理。
catch返回的Promise状态参考上面then的返回值
例如:

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 可以捕抓到前面的出现的错误。
    console.log(err.toString());
});

//执行结果如下:
// p
// Error: then1 

Promise对象的Error对象具有传递性,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
当出错时,catch会先处理之前的错误,然后通过return语句,将值继续传递给后一个then方法。我们在上面例子的catch语句后再添加一个then语句,看看会出现什么结果。

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 可以捕抓到前面的出现的错误。
    console.log(err.toString());
    return 'err next';
}).then(data=>{
    console.log(data);
});
//执行结果为
//p1
// Error: then1 
//err next 

注意!!!这里有个陷阱等着你往下跳

Promise的错误处理是一种绝望的设计。默认情况下,它假定你想让所有的错误都被Promise的状态吞掉,而且如果你忘记监听这个状态,错误就会默默地凋零/死去。
这时你可能会想到把catch语句写在Promise链最后面来解决,像这样:

.......

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( errors => {
    // 可以捕抓到前面的出现的错误。
    console.log(errors.toString());
});

要是catch里面函数本身也有错误呢?谁来捕获它?还有一个没人注意的promise:catch(..)返回的promise,我们没有对它进行捕获,也没注册拒绝处理器。仅仅将另一个catch(..)贴在链条末尾,悬挂着一个困在未被监听的Promise中的,未被捕获的错误,即便这种可能性大大减少。

  • 处理未被捕获的错误
    Promise应当增加一个done(..)方法,它实质上标志着Promise链的“终结”。done(..)不会创建并返回一个Promise,所以传递给done(..)的回调很明显地不会链接上一个不存在的Promise链,并向它报告问题。done(..)的拒绝处理器内部的任何异常都作为全局的未捕获错误抛出(基本上扔到开发者控制台),这就和try catch(){ }差不多了。

done()方法我们下一次再介绍,关于Promise对象先讲到这里,下一次我们继续一起学习Promise。
谢谢观看!!!

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

推荐阅读更多精彩内容

  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,126评论 0 16
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,078评论 0 12
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,328评论 0 19
  • 9月15日 周四 天气晴朗 海上生明月,天涯共此时。 祝大家中秋快乐。 今年,我确实跑到海边,看着明月高悬...
    penny胖妮阅读 140评论 1 1
  • 百日计划8号开始执行,5天回顾, 01:每日书坚持在写,5天,每天最少6个字的隶书,小朋友签字确认。每天写简书,当...
    张瓓阅读 202评论 0 0