Damon的es6学习之路 -- promise对象学习 (day 4)

本文学习资料等来源于阮一峰的《ECMAScript 6入门》网络版,链接地址:http://es6.ruanyifeng.com/#docs/promise

Promise 的含义

因为学习步骤本身就是跟随阮一峰的介绍过程,所以第一个标题索性也使用的原文的标题。
简单来说Promise对象其实就是处理异步操作的的一种方案。它本身可以理解成一个容器,将一个或多个promise对象放入一个容器中,再进行进一步的异步操作,等待有返回值时,再将结果或者异常抛出。原文中这个概念的的解释其实相当通俗易懂:

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

promise的基础用法

直观点理解promise,其实它本身的意义更多的就是个异步操作的合集,然后将异步操作的结果信息反馈出来。
promise实际上是有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),只有当异步操作有结果之后,才能使用这个结果去改变它的状态,其他任何方式都无法去强制改变promise的状态。也是“承诺”的意义所在。

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

最简单的示例,直接引用阮一峰的示例代码:

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

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

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve的作用:在异步操作成功后调用,直接将promise对象的状态从pending(进行中)改变为fulfilled(已成功),并将括号内的结果作为参数抛出。
reject的作用:在异步操作失败后调用,直接将promise对象的状态从pending(进行中)改变为rejected(已失败),并将括号内的结果作为参数抛出。


在promise实例生成后,即状态改变有返回值后,可以用then方法获取resolved的返回值catch方法获取reject的返回值

阮一峰的原文中有更加详细的描述,其实只需要通过then方法就可以指定获取resolved和reject的值,因为then可以接受两个回调函数,第一个必要的回调函数就是获取resolved的返回值的,第二个可选的回调函数就是指定获取reject返回值的。而原文中也明确建议使用catch的方式捕获reject的返回值,所以我直接将其整合写在一起,省略了其中的实验和对比过程,直接总结结果。

下面是示例代码

promise = new Promise(function(resolve, reject) {
  let boo = true

  if (boo ){
    resolve(1);
  } else {
    reject(2);
  }
}).then((res) => {
	console.log(res)
}).catch((err) => {
	console.log(err)
})

// 打印结果
1

因为boo为true,调用resolve方法,将promise状态改变为成功,随后结果1被抛出给then调用,被打印出来。
如果boo为false,则会调用reject方法,promise状态则会变为失败,结果2会被抛出,被catch捕捉到。
另外,catch不只是调用reject方法后才会执行,它本身的意义是promise状态为失败即调用,即使没有主动调用reject,当promise内部代码运行报错时,抛出的错误依旧会被catch捕捉到。示例如下:

new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
}).then((r) => {
	console.log(r,'r')
}).catch((e) => {
	console.log(e,'e')
})

// 打印结果
ReferenceError: x is not defined
    at Promise.then (<anonymous>:3:13)
    at new Promise (<anonymous>)
    at <anonymous>:1:1 "e"

另外有两点值得注意,在原文中也有描述

  1. promise新建后就会立即执行。
  2. 调用resolve或reject并不会终结 Promise 的参数函数的执行。

promise在被创建出来后,可以把它整体看做一个版块,这个版块和外层代码是属于同一队列的,都是被同步调用的。这里直接引用阮一峰的示例。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

即是说,promise内部执行的代码,和最后打印“Hi”的代码是同一队列,同步操作的。而then里面的代码是被resolve调用后抛出的,属于异步代码,会被放在同步队列后执行。
调用resolve或reject并不会终结 Promise 的参数函数的执行。这两个改变状态的代码自身并不会像return一样终止代码的运行(所以大多数使用的时候会直接return掉),当然,抛出的代码依旧是属于异步,会在同步代码后执行。示例代码也可以直接看阮一峰的示例,很清晰明了。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

promise的特点
  1. 对象的状态不受外界影响。
  2. 一旦状态改变,就不会再变。

关于第一点,对象的状态不受外界影响的理解,我的理解是promise内部的所有操作,最终会根据状态抛出,哪怕过程中报错了,依旧不会影响promise对象以外的代码。普通的报错代码,在错误发生时,浏览器就会抛出异常,退出进程、终止脚本执行,而终止后续代码的读写,而promise不会,即使报错,它依旧会执行下去,最终将错误信息抛出,我们只需要捕捉错误信息就可以了。
注意: 实验证明,promise内部报错后,其后续代码同样会被终止,只是不会影响外部而已。

new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
	console.log(666)
}).then((r) => {
	console.log(r,'r')
}).catch((e) => {
	console.log(e,'e')
})

// 打印结果
ReferenceError: x is not defined
	at Promise.then (<anonymous>:3:13)
	at new Promise (<anonymous>)
	at <anonymous>:1:1 "e"

第二点,一旦状态改变,就不会再变的理解。我的理解是promise一旦抛出resolve(成功)或reject(失败),其状态就不会再次改变。即是说,promise本身这个容器可以理解为一次性的,当然它可能是链式的多层使用,但每次调用时,当前promise对象只有一个状态且只能改变一次。所以,我可以将其粗略理解为promise的一次性,以下是示例:

new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(1);
	reject(2)
}).then((r) => {
	console.log(r,'r')
}).catch((e) => {
	console.log(e,'e')
})

// 打印结果
1 "r"

示例代码中,先调用了resolve,讲promise的状态转变成了成功,接着调用reject,试图promise状态改变为失败。各自抛出1、2方便区分,最终打印出来的是1,因为promise的状态先改变成了成功后,就不会再被改变了。


then方法

上文已经提到过then方法的功能,它主要是处理promise成功的回调函数。值得一提的是,then获取返回值后,还可以继续调用then。因为then方法返回的是一个新的promise对象,这个对象还可以继续调用新的then方法,这种做法就可以实现链式操作。

new Promise(function(resolve, reject) {
    resolve(2);
}).then((r) => {
	return (r * r);
}).then((n) => {
	return (n * n);
}).then((x) => {
	console.log(x)
})

// 打印结果
16

值得一提的是,这种链式写法,resolve只需要调用一次,它就已经将状态改变为成功了,后面的的then操作都是在resolve的基础上进行的(特别鸣谢:这里是来自小媳妇的讲解),后续的调用,希望将参数抛出的时候,直接使用return就可以了,再使用resolve反而会报错。


catch

同样提到过的东西,catch的主要作用是处理promise失败的回调函数,它需要注意的有两点,第一点:只有当状态为失败时,才会被catch捕获到,如果状态先已经变成成功,再调用失败,是无效的(前文提到过,promise的特点第一条)。这里直接使用阮一峰的示例就很一目了然。

const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

第二点:promise的catch是具有冒泡属性的,会一直向后传递,直到被捕获为止。即是说,一个promise构造函数,其实只需要写一个catch即可。
示例1:多个catch,被捕获时直接打出。

new Promise(function(resolve, reject) {
    reject('失败');
}).catch((r) => {
	console.log(r, 1);
}).catch((n) => {
	console.log(n, 2);
}).catch((x) => {
	console.log(x, 3);
})

// 打印结果
失败 1

示例2:一个catch,捕获第一个失败信息

new Promise(function(resolve, reject) {
    reject('失败');
}).then((r) => {
	console.log(r, 1);
}).then((n) => {
	console.log(n, 2);
}).then((x) => {
	console.log(x, 3);
}).catch((e) => {
	console.log(e,'e')
})

// 打印结果
失败 e

示例3:一个catch,捕获中间的错误信息

new Promise(function(resolve, reject) {
    resolve('成功');
}).then((r) => {
	console.log(r, 1);
}).then((n) => {
	return x;
}).then((x) => {
	console.log(x, 3);
}).catch((e) => {
	console.log(e,'e')
})

// 打印结果
成功 1
ReferenceError: x is not defined
    at Promise.then.then (<anonymous>:6:2) "e"

finally

finally方法其实就是无视promise的状态,无论成功还是失败,都会调用它。

new Promise(function(resolve, reject) {
    resolve('成功');
}).then((r) => {
	console.log(r, 1);
}).catch((e) => {
	console.log(e, 2)
}).finally((f) => {
	console.log('我是finally')
})

// 打印结果
成功 1
我是finally
new Promise(function(resolve, reject) {
    reject('失败');
}).then((r) => {
	console.log(r, 1);
}).catch((e) => {
	console.log(e, 2)
}).finally((f) => {
	console.log('我是finally')
})

// 打印结果
失败 2
我是finally

all

promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
all方法的介绍直接使用阮一峰的原文介绍。

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

Iterator接口本文暂不研究。
p的状态是根据p1,p2,p3来决定的。当三个参数状态都是成功的时候,p才会变成成功状态。当任意一个参数状态是失败的时候,则p的状态便是失败。
p状态变成成功时,p1、p2、p3的返回值会组成一个数组,传递给p的回调函数。
示例(代码测试环境为浏览器的Console区域)

var testList = [1+1,2+2,3+3];
Promise.all(testList).then((res) => {
	console.log(res);
}).catch((err) => {
	console.log(err);
})

// 打印结果
// 2
// 4
// 6

race

race方法,类似all方法,同样可以将多个promise对象包装成一个promise对象。它的作用在阮一峰的文章里的解释大致是:多个对象只要有其中一个率先改变promise的状态,即将该对象的返回值传递回来。
相当类似es6循环中的some,不过some循环是判断布尔值,只要某一个值为true则返回。而race方法是只要某一个对象改变了promise的状态,即返回。这里就不着重介绍了。


resolve/reject

前文已经使用过很多次,resolve方法主要是将promise的状态修改为成功,reject方法主要是将promise的状态修改为失败。它们的参数都会被抛给构造函数,分别由then和catch打印(二次重申,其实then都可以打印,但便于记忆和区分以及规范,then最好只处理成功回调,catch处理失败回调)。
值得注意的是,resolve和reject方法还具有个功能,就是可以将传入的对象转换为promise对象,同时promise的状态变为成功/失败。如果传入的对象是thenable对象(thenable对象指的是具有then方法的对象),转换后会立即执行该对象的then方法。
原文示例

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

另外一点需要注意的是resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
原文示例

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

原文解释

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。

暂时粗略理解为:resolve的异步操作形成的队列优先级是高于setTimeOut异步操作的,会在同步队列执行完成后,优先执行resolve的异步操作。至于这个优先级是针对整个promise构造函数还是只针对resolve/reject,还需要进一步测试。


关于promise对象的学习暂时就告一段落。其实本身promise函数勉强会照猫画虎的使用,但经过这次学习,理解得更深入些。相信随着更频繁的使用,会更加得心应手。加油~

一个佛系的前端的自学笔记
2019.07.09

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值