Promise笔记

Promise的含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

上面两段都是阮一峰那本教程上的原话,我觉得说的算是清楚的,不知道从什么时候开始有这种感觉:如果不清楚一个东西的定义,那我对这个东西就不能说了解,虽然看了之后照着样子也能用,可始终也不觉得踏实。所以了解一个东西之前,无论如何得知道它是什么,如果做到用自己的话尝试给东西下个定义,那大概就做好了第一步。

从上面两段话可以得到一些信息:Promise(泛指的话)是一种异步编程的解决方案;这种方案比传统的解决方案(回调函数和事件)更合理、强大;它已经由ES6写入标准,而且统一了用法;标准里提供 了Promise对象供我们使用,通过对这个对象的使用我们就可以实现这种Promise解决方案的异步编程。所以Promise又可以特指Promise对象,那下面说的主要就是这个Promise对象。

Promise对象

那这个对象怎么生成的?原生提供了Promise这个构造函数,通过这个构造函数就可以new 出一个Promise对象实例

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

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

Promise构造函数以一个函数为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve的作用是将Promise对象的状态从pending变为成功,从而执行后面的then()里的第一个参数函数;reject的作用是将Promise对象的状态从pending变为失败,从而执行后面的catch或者then()的第二个参数函数。所以当异步操作成功时调用resolve(value)value作为参数传给successFn(成功时的回调函数,作为then()的第一个参数)并调用之;当异步操作失败的时候调用reject(e)并将e作为参数传给failureFn(失败时的回调函数,作为then()的第二个参数或catch()的参数)并调用之。

举个例子:

function timeout(ms1, ms2) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms1, ms1 + 'ms 后我会被打印出来')
    setTimeout(reject, ms2, new Error(ms2 + 'ms 后我会被打印出来'))
  });
}

timeout(3000, 4000).then((value) => {
  console.log(value)
}).catch( (e) => console.log(e))
//3s后打印 `3000ms 后我会被打印出来`

这里如果传入的参数ms1>ms2时,就会调用catch((e) => console.log(e)),因为在ms2 ms之后先调用了setTimeout(reject, ms2, new Error(ms2 + 'ms 后我会被打印出来'))将Promise对象的状态由pending改为失败的状态了。

此外,除了new Promise()的方式来创建对象,我们还可以通过Promise.resolvePromise.reject两个方法来快速创建一个Promise对象:

Promise.resolve('lalala').then(console.log)

上面代码会立即打印出'lalala',虽然目前不知道这种方式有什么用。

then()

上面说了then()方法接受两个函数,一个是异步操作成功时的回调函数,一个是异步操作失败时的回调函数,两个都可选。那then()方法的返回值是什么呢?then()返回的是一个新的Promise对象,正因为此所以我们可以执行一系列的链式操作,但是其返回的Promise对象的行为与then()中的回调函数的返回值有关,下面是MDN上的内容:

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变为终态时的回调函数的参数是相同的。

一一举例:

回调函数返回值为value的情况

Promise.resolve('la').then(value => value + value)
.then(value => console.log(value + value))  //打印lalalala
.then(console.log)  //打印undefined

第一个then的回调函数返回值是'lala',第二个then的回调函数返回值是 undefined,所以每个then依次执行

回调函数抛出错误的情况

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'lalala')
  });
}
timeout(1000).then(value => {throw new Error(value)})
.then(console.log, (e) => console.log(e.toString()))//打印Error:lalala

上面timeout()返回的Promise对象1000ms后执行throw new Error(value),即then()的回调函数抛错,然后这个错误被第二个then()捕获,并调用 (e) => console.log(e.toString()),说明第一个then()返回的Promise对象因回调函数的抛错而变成失败状态

回调函数返回已经接受状态的Promise的情况

Promise.resolve('lala').then((value) => Promise.resolve(value + 'haha'))
.then(value => console.log(value + ': 我是从上一个then的回调函数中传过来的')) 
//打印lalahaha: 我是从上一个then的回调函数中传过来的

第一个then()的回调函数处理返回的是一个新的Promise对象,这个对象的状态是成功的,并且将resolve('lalahaha')的参数传给下一个then()successFn()

回调函数返回已经拒绝状态的Promise的情况

Promise.resolve('lala').then((value) => Promise.reject(value + 'haha'))
.then(value => console.log(value + ': 我是从上一个then的回调函数中传过来的')) 
.catch(value => console.log(new Error(value)))
//打印一个Error

跟第三种情况类似,不赘述

前面四种then()的回调函数的返回情况几乎都是同步的操作,没有涉及到异步,所以比较简单。then()的回调函数的返回的Promise对象又涉及到异步操作的情况才是常见的情况,比如我们异步访问服务端,获取数据A,然后有根据数据A再去访问服务端获取数据B,下面用setTimeout来模拟异步。

回调函数返回一个未定状态(pending)的Promise的情况

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

timeout(1000, 'lala').then((value) => {
  console.log(value)  //1000ms后打印lala
  return timeout(2000, 'haha' + value)
}).then(console.log) //3000ms后打印hahalala

第一个then()返回一个状态未定的Promise对象,该Promise对象状态2000ms后变为成功状态,所以2000ms后调用第二个then()的对应的回调函数,而且回调函数的参数是由第一个Promise对象的resolve()传过来的。除了这种后一个操作只依赖一个异步请求的情况,我们还会遇到后一步操作依赖多个异步请求的情况,而以来多个异步请求的情况又分两种:

  • 后一步操作依赖所有异步请求的结果,即必须等到所有异步请求(为简便我们假设这些请求彼此互不依赖,即都是并行的)返回结果后才能执行后一步操作
  • 后一步操作只依赖任意一个异步请求的结果,即只需某一个异步请求(为简便我们假设这些请求彼此互不依赖,即都是并行的)返回结果后就能执行后一步操作

对于第一种情况,Promise对象为我们提供了.all()方法

all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例:
var p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p的状态由p1p2p3决定,只有当p1p2p3的状态都为成功的时候,p的状态才为成功,此时p1p2p3的返回值组成一个数组,传递给p的回调函数;否则,只有要任何一个的状态为失败,那p的状态就变成失败,此时第一个失败的实例的返回值,会传递给p的回调函数。下面看两个例子:

function timeout(ms, data) {
  return new Promise((resolve, reject) => {
    console.log(new Date())
    setTimeout(resolve, ms, data)
  })
}
function timeout1(ms, data) {
  console.log(new Date())
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, data)
  })
}
//成功情形:
Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))

/*打印
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
["kakaka", "lalala", "hahaha"] Wed Nov 01 2017 18:24:00 GMT+0800 (中国标准时间)
*/

//失败情形:
Promise.all([timeout(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))

/*
打印
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)

Error: lalala
    at Promise.all.catch.data (<anonymous>:1:117)
    at <anonymous> Wed Nov 01 2017 18:28:01 GMT+0800 (中国标准时间)
*/

第一个为什么是3s不是6s,第二个为什么是2s不是3s,不是单线程吗?好吧,应该是setTimeout的机制没搞清楚。下面我们来看race方法:

race

.all()不同的是,.race()接受的数组里的Promise对象只有要任何一个改变状态,不管成功还是失败,就会执行对应的回调函数:

//成功情形:

Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))

/*
打印:
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
kakaka Wed Nov 01 2017 18:34:42 GMT+0800 (中国标准时间)
*/

//失败情形:
Promise.all([timeout1(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))

/*
打印:
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Error: kakaka
    at Promise.all.catch.data (<anonymous>:1:118)
    at <anonymous> Wed Nov 01 2017 18:36:01 GMT+0800 (中国标准时间)
*/

好了,这次就先到这,后面还要再看看下Promise.resolve()Promise.reject()还需要了解原生ajax对这两种情况的处理方式。

参考:
http://es6.ruanyifeng.com/#docs/promise
http://www.cnblogs.com/lvdabao/p/es6-promise-1.html

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

推荐阅读更多精彩内容