JavaScript简餐——初见Promise


前言

写本《JavaScript简餐》系列文章的目的是记录在阅读学习《JavaScript高级程序设计(第4版)》一书时出现的各个知识点。虽是对读书的笔记和总结,但是希望它轻量、简洁、犀利,不会引起阅读疲劳,可以在碎片化时间和闲暇之余轻巧地沐浴一下知识点。每篇文章只针对一个小部分进行讲解式的梳理,来达到个人复习总结和分享知识的目的。


一、Promise基础

Promise是ES6中新增的一种异步编程机制,可以抽象地表示异步操作,其新增了引用类型Pormise,可以通过new操作符来实例化。(Promise也被翻译为期约)。创建Promise对象时需要传入执行器函数作为参数,不给出这个函数参数会报错:SyntaxError。其实例化方法如下,这里给它传个空函数:
const p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise { <pending> }(setTimeout的第三个参数时传给第一个函数的参数)

1.Promise状态机

在上面的例子的输出中可以看到Promise { <pending> },其中的pending就表明实例处于pending(待定)状态。Promise是一个有状态的对象,可能处于如下3种状态之一:
  • pending(待定)
  • fulfilled / resolved(解决)
  • rejected(拒绝)

pending是Promise的最初状态。在pending状态下,Promise可以落定为resolved或者rejected状态,resolved表示已经成功完成,而rejected表示没有成功完成,无论落定为哪种状态都是不可逆的。我们可以联想到实际的异步操作中:当我们请求一个API时,HTTP状态码为200时将Promise的状态更改为resolved,其他情况下我们就将状态变更为rejected。另外,Promise的状态也不能被外部JavaScript代码修改,这是故意为之的,目的是为了隔离外部的同步代码。

2.通过执行函数 控制Promise的状态

由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是通过它的两个函数参数实现的。这两个函数参数通常都命名为resolve和reject,正如下面这样:
const p1 = new Promise((resolve, reject) => { resolve()});
const p2 = new Promise((resolve, reject) => { reject()});
setTimeout(console.log, 0, p1); // Promise {<fulfilled>: undefined}
setTimeout(console.log, 0, p2); // Promise {<rejected>: undefined}
调用resolve()会把状态切换为resolved(输出中之所以是fulfilled是因为我是在Chrome环境中运行的,其实fulfilled和resolved是等价的),调用reject()会把状态切换为rejected。另外,调用reject()也会抛出错误。我们还可以在两条输出结果中都看到undefined,这是因为Promise的状态在切换为resolved时就会有一个私有的内部值,而在切换为rejected时就会有一个私有的内部理由,这二者不可修改且都是可选的,默认值均为undefined。如果我们在调用resolve()和reject()时给一个参数,那么这个参数就会作为内部值或者内部理由展现出来了:
const p1 = new Promise((resolve, reject) => { resolve(1)});
const p2 = new Promise((resolve, reject) => { reject(2)});
setTimeout(console.log, 0, p1); // Promise {<fulfilled>: 1}
setTimeout(console.log, 0, p2); // Promise {<rejected>: 2}
resolve()和reject()中的无论哪个被调用,状态都不可撤销,继续修改会静默失败,如下所示:
const p = new Promise((resolve, reject) => {
  resolve();
  reject(); // 静默失败了,最终状态还是resolved
})
setTimeout(console.log, 0, p); // Promise {<fulfilled>: undefined}

3.Promise.resolve()

Promise并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。通过调用Promise.resolve()静态方法,可以实例化为一个解决的Promise对象。下面两个Promise对象实际上是一样的:
const p1 = new Promise((resolve, reject) => { resolve(1)}); 
const p2 = Promise.resolve(2) 

setTimeout(console.log, 0, p1); // Promise {<fulfilled>: 1}
setTimeout(console.log, 0, p2); // Promise {<fulfilled>: 2}
另外,对于resolve()来说它可以把任何值都转换为一个Promise对象,多余的参数会直接忽略,若传入的参数本身是一个Promise对象,那它的行为就类似于一个空包装,因此resolve()可以说是一个幂等方法:
const p1 = new Promise((resolve, reject) => { resolve(1, 2, 3)}); 
const p2 = Promise.resolve(2, 3, 4) 
const p3 = new Promise((resolve, reject) => { resolve(p1)}); 
const p4 = Promise.resolve(p2) 

setTimeout(console.log, 0, p1); // Promise {<fulfilled>: 1}
setTimeout(console.log, 0, p2); // Promise {<fulfilled>: 2}
setTimeout(console.log, 0, p3); // Promise {<fulfilled>: 1}
setTimeout(console.log, 0, p4); // Promise {<fulfilled>: 2}

4.Promise.reject()

与Promise.resolve()类似,Promise.reject()会实例化一个拒绝的Promise对象并抛出一个异步错误(这个错误不能通过try/catch捕获,而只能通过拒绝处理程序捕获,下一篇会讲到具体的实例方法)。下面两个Promise对象实际上是一样的:
const p1 = new Promise((resolve, reject) => { reject(1)}); 
const p2 = Promise.reject(2) 
setTimeout(console.log, 0, p1); // Promise {<rejected>: 1}
setTimeout(console.log, 0, p2); // Promise {<rejected>: 2}
与resolve()一样,reject()也会忽略多余的参数,但是关键在于reject()并没有照搬resolve()的幂等逻辑。如果给他传一个Promise对象,则这个Promise对象会成为它拒绝的理由:
const p1 = new Promise((resolve, reject) => { reject(1, 2, 3)}); 
const p2 = Promise.reject(2, 3, 4) 
const p3 = new Promise((resolve, reject) => { reject(p1)}); 
const p4 = Promise.reject(p2) 

setTimeout(console.log, 0, p1); // Promise {<rejected>: 1}
setTimeout(console.log, 0, p2); // Promise {<rejected>: 2}
setTimeout(console.log, 0, p3); // Promise {<rejected>: Promise}
setTimeout(console.log, 0, p4); // Promise {<rejected>: Promise}

5.同步/异步执行的二元性

Promise的设计很大程度上会导致一种完全不同于JavaScript的计算模式。下面的例子展示了这一点,其中包含了两种模式下抛出错误的情形:
try {
  throw new Error('We got Error!');
} catch(e) {
  console.log(e); // Error: We got Error!
}

try {
  Promise.reject(new Error('We got Error!'));
} catch(e) {
  console.log(e); // Uncaught (in promise) Error: We got Error!
}
第一个try/catch块抛出并捕获了错误,第二个try/catch块抛出错误却没有捕获到。这里之所以没有捕获到错误是因为它没有通过异步模式捕获错误。从这里就可以看出Promise真正的异步特性:它们是同步对象,但也是异步执行模式的媒介。再具体点说就是,拒绝期约的错误并没有抛到同步代码的线程里,而是通过浏览器异步消息队列来处理的,因此,try/catch块并不能捕获该错误。(如果不能理解,可以去看看介绍JS中的事件循环以及宏任务和微任务方面的文章。)代码一旦开始以异步模式执行,则唯一与之交互的方式就是使用异步结构,对于Promise对象来说就是其实例方法(下一篇来详细介绍),例如下面这样:
Promise.reject(new Error("We got Error!")).then(null, e => {
  console.log(e); // Error: We got Error!
});
像这样用then方法处理reject()抛出的错误就可以将其捕获到了。

总结

以上就是今天要讲的内容,今天简单介绍了一下Promise的概念、状态、改变状态的方法( resolve()和reject() )以及它的异步特性。下一篇我们来讲一下Promise中的那些实例方法。撒花~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值