Promise小记

一、前言

以往的经验告诉我,在接触自己比较陌生的名词和技术前首先要问三个问题:

  1. 它是用来做什么的?
  2. 它是如何实现的?
  3. 没有它,应该怎么办?

今天我们主要关注第一个问题点,浅析一下Promise的使用场景和一些特点。

二、定义

A Promise is an object representing the eventual completion or failure of an asynchronous operation.

这是MDN上对Promise的描述:Promise 是一个对象,它表现了一个异步操作最终的完成状态或者失败状态。
简单点说,Promise是为了更好地写异步操作而产生的,它保存着一个未来才会结束的事件的结果。

三、特点

  1. 对象状态不受外界影响,Promise对象有三种状态,pending(进行中)、fulfilled(成功)和rejected(失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

2.一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已完成)或者 settled。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

tips:很多教程里把resolved(已完成)等价于fullfilled(成功)状态,下文中Promise状态定型为resolved包含fullfilled和rejected两种状态。但是由于习惯写法,Promise中“成功”的回调函数的名字依然叫做resolve。

四、使用

在使用Promise之前我们先看看我们在没有Promise时是如何写异步操作的,以ajax请求为例子:
eg1:

$.ajax('url1')
.done((res)=>{
    $.ajax('url2')
    .done((res)=>{
        $.ajax('url3')
        .done((res)=>{
            console.log(res)
        })
    })
})

这种有“层次感”的代码即callback hell,一旦嵌套三层以上就十分影响可读性,也容易出错。
再看看Promise如何实现上面的需求:
eg2:

Promise.resolve($.ajax('url1'))
       .then((res)=>$.ajax('url2'))
       .then((res)=>$.ajax('url3'))
       .then((res)=>console.log(res))

如上,我们可以看到代码不再是层层嵌套,这样写异步操作更为直观。

1.基本用法

eg3:(先上代码)

const makePromise = ()=>{
return new Promise((resolve,reject)=>{
    setTimeout(()=>{
          let num = Math.random()*10
    if(num >5){
        resolve('resolve--->' + num)
    }else{
            reject('reject--->' + num)
        }
    })
 },2000)
}

makePromise()
.then(str=>console.log(str),err=>console.log('oh no' + err)) //resolve--->8.866619911022287

说明:

  1. Promise是一个构造函数,使用new可以生产Promise实例;
  2. 构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
  3. resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fullfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  4. Promise实例生成以后,可以用then方法分别指定fullfilled状态和rejected状态的回调函数。(then的参数是两个回调函数)

2. 使用Promise.prototype.catch()捕获错误

Promise有3个缺点:

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

现在我们针对第二点展开Promise实例的catch方法的使用
eg4:

makePromise()
    .then(res=>$.ajax1(res))
    .then(res=>$.ajax2(res))
    .catch(err=>console.log(err)) //可以捕获前面所有Promise对象的error

说明:

  1. catch一般在Promise链的最后一步调用,它可以捕获前面任何一个Promise对象的error;
  2. 虽然then的第二个参数可以获取上一个Promise的error,但是不提倡这么写;应为catch可以捕获上面多个Promise的error,写法也更容易理解;
  3. Promsie实例执行完catch方法后,也会变成fullfilled;

eg5:

//bad
makePromise()
    .then(res=>console.log(res),err=>console.log(err))

//good
makePromise()
    .then(res=>console.log(res))
    .catch(err=>console.log(err))

3.学会使用Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
eg6:

const newPromise = Promise.all([promise1,promise2,promise3])

newPromise的状态由promise1,promise2,promise3共同决定;
分以下两种情况:

  1. 只有promise1、promise2、promise3的状态都变成fulfilled(完成),newPromise的状态才会变成fulfilled,此时promise1、promise2、promise3的返回值组成一个数组,传递给newPromise的回调函数。
  2. 只要promise1、promise2、promise3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
    eg7:
let arr  = [1,2,3]
let promiseArr = arr.map((item=>makePromise(item))
Promise.all(promsieArr)
    .then(resArr=>console.log(resArr.length)) //3

上面的例7中只有当promiseArr中的三个Promise实例的状态定型(resolved,包含fullfilled和rejected两种状态)才会进入进入Promise.all后面的回调:

  1. 如果3个Promise实例的定型状态为fullfilled那么,Promise.all()得到的实例状态也会是fullfilled,3个Promise实例的返回值会放在一个数组中,作为入参传给Promise.all后面then的第一个回调函数;
  2. 如果3个Promise实例定型后,有任何一个实例的状态为rejected,那么Promise.all()得到的实例状态为rejected,第一个为rejected的Promise的返回值会传给Promise.all后面then的第二个回调函数;

有一点值得注意的是:

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

原因在三.2中的说明里提到了:“Promise实例调用catch方法后,状态会变为fullfilled”。也就是说在没调用catch方法状态为rejected的Promise实例,调用catch后状态变为了fullfilled,Promise.all()的状态会变成fullfilled,当然不会触发Promise.all()后面的catch了。
eg8:

let promise1 = new Promise((resolve,reject)=>{
     resolve('hello')
}).then(res=>res)
  .catch(err=>err)
let promise2 = new Promise((resolve,reject)=>{
     throw new Error('this is an error')
}).then(res=>res)
  .catch(err=>err)

let newPromise = Promise.all([promise1,promise2])
newPromise.then(res=>console.log(res)) 
          .catch(err=>console.log(err))
/*
  ["hello", Error: this is an error
    at Promise (<anonymous>:7:12)
    at new Promise (<anonymous>)
    at <a…]
*/

可以看到打印的结果是一个数组,代表newPromise的完成后的状态为fullfilled,两个Promise实例的返回值作为入参传给了then,而newPromise后面的catch并不会捕获到promise2中的error,应为promise2自己catch了error。
学了这么多理论,下面说一下Promise.all的使用场景:
一个网页,需要加载三个数据源的内容(图片、文字、背景音乐...),它们之间是没有依赖关系的,加载完成后取消loading:

//请求图片的Promsie
let promisePic = new Promise((resolve,reject)=>{
     $.ajax('url1').done(res=>resolve(res))
                   .fail(err=>reject(err))
})

//请求文字的Promise
let promiseText = new Promise((resolve,reject)=>{
     $.ajax('url2').done(res=>resolve(res))
                   .fail(err=>reject(err))
})

//请求背景音乐的Promise
let promiseBgm = new Promise((resolve,reject)=>{
     $.ajax('url3').done(res=>resolve(res))
                   .fail(err=>reject(err))
})

let promiseLoad = Promise.all([promsiePic,promiseText,promiseBgm])
promiseLoad.then(()=>{clearLoading()})
           .catch((err)=>console.log(err))

4.更多方法

Promise.prototype.finally(fn)
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
Promise.race([p1, p2, ...])
和Promise.all类似,Promise.race方法同样是接收多个 Promise 实例,但它返回最先fullfill的Promise的结果,如果有一个reject,就提前reject。
Promise.resolve(value)
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promsie.reject(value)
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

5.新的提案(proposal)[2019.8.28更新]

Promise.allSettled([p1, p2, ...]):
和Promise.all()类似,接受由Promise对象组成的数组,Promise.all()会在所有的Promise状态为fullfilled或者其中一个状态为rejected时返回,但是它不会管数组中Promise的状态,只要所有的Promise 状态为settled(fullfilled or rejected)。

tips: This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.

Promise.any([p1, p2, ...]):
Promsie.any()和Promise.race()类型,接受一个由Promise对象组成的数组,当其中任何一个状态变为fullfilled时,就会返回这Promise的结果。但是和Promise.race()不同的是,它在有Promise状态为rejected时也不会立即返回,只有在所有Promise reject时才返回rejected 的Promise。

五、总结

1.介绍了Promise的作用、定义和特点;
2.列举了Promise的简单使用和错误捕获方法;
3.简单列举了Promise部分方法的使用场景;

文中可能有不大严谨的地方,欢迎大家指出。
关于Promise的更多讲解可以参考

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

推荐阅读更多精彩内容

  • 前言 本文旨在简单讲解一下javascript中的Promise对象的概念,特性与简单的使用方法。并在文末会附上一...
    _暮雨清秋_阅读 2,180评论 0 3
  • 1.promise简介 1.1 Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果...
    常青_1f93阅读 822评论 0 1
  • 你不知道JS:异步 第三章:Promises 接上篇3-1 错误处理(Error Handling) 在异步编程中...
    purple_force阅读 1,384评论 0 2
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,693评论 0 5
  • 走在 节日的街道上 街道并不知道自己的 节日,只有你我 相视一笑 一朵朵神秘的纹身 在夜的深腹处绽放 大地呢喃着 ...
    荆弃剑阅读 299评论 0 0