美文网首页
Promise笔记

Promise笔记

作者: YeLqgd | 来源:发表于2017-11-01 18:40 被阅读0次

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

相关文章

  • Promise,async,await笔记

    Promise,async,await笔记 Promise 创建promise对象 Promise对象构造方法传入...

  • promise用法

    Promise笔记 1.promise构造函数 Promise是一个构造函数,传参是一个function(reso...

  • promise

    本文是整理阮一峰大神ES6中 Promise 的学习笔记 目录: Promise.prototype.then()...

  • Promise 笔记

    笔记 1. 构造方法 创建promise对象的流程如下����: new Promise(fn) 返回一个prom...

  • Nodejs Promise 读书笔记

    Nodejs Promise 读书笔记 前言 Promise是抽象异步处理对象以及对其进行各种操作的组件。(Pro...

  • 不深入只浅出ES6 Promise | 笔记

    用例子直观的陈列 Promise 的行为作为笔记(如果能帮助新手快速了解 Promise 的使用自然最好,最终还是...

  • es6 Promise 学习笔记2 链式调用

    es6 Promise 学习笔记1 基础代码解析 这期讲个复杂点的例子 then里的回调返回一个 Promise ...

  • Promise笔记

    Promise学习(上): 资料: JavaScript Promise迷你书 原著:azu / 翻译:liubi...

  • Promise笔记

  • Promise笔记

    Promise的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强...

网友评论

      本文标题:Promise笔记

      本文链接:https://www.haomeiwen.com/subject/fwfgpxtx.html