JavaScript是一种同步的、阻塞的、单线程的语言,在这种语言中,一次只能执行一个操作。但web浏览器定义了函数和API,允许我们当某些事件发生时不是按照同步方式,而是异步地调用函数(比如,时间的推移,用户通过鼠标的交互,或者获取网络数据)。这意味着代码可以同时做几件事情,而不需要停止或阻塞主线程。
传统的异步编程方式是基于回调函数实现的,但是由于“回调地狱”问题,这种方式显得不够优雅。所以在 ECMAScript 6 中新增了 Promise 引用类型以及 async/await 关键字定义的异步函数机制,支持优雅地定义和组织异步逻辑。
一、前言
在学习计算机相关课程时,我们经常会遇见同步/异步相关的概念,那么到底什么是同步/异步呢?在正式引出本文的主人公Promise 引用类型以及 async/await 关键字之前,我们先来了解一些有关异步的基础知识。
1.1 异步的定义
MDN中对异步的定义:
异步指两个或两个以上的对象或事件不同时存在或发生(或多个相关事物的发生无需等待其前一事物的完成)
维基百科中对异步的定义
Asynchrony, in computer programming, refers to the occurrence of events independent of the main program flow and ways to deal with such events. These may be "outside" events such as the arrival of signals, or actions instigated by a program that take place concurrently with program execution, without the program blocking to wait for results. Asynchronous input/output is an example of the latter case of asynchrony, and lets programs issue commands to storage or network devices that service these requests while the processor continues executing the program. Doing so provides a degree of parallelism.
本人在一篇博客中看到对异步的定义:
异步描述了两个或多个事件/对象之间的关系,这些事件/对象确实在同一系统内交互但不以预定间隔发生并且不一定依赖彼此的存在才能发挥作用。它们没有相互协调,这意味着它们可以同时发生,也可以不同时发生,因为它们有自己独立的议程。
通过以上的定义,简单来说,异步就是事件/对象之间相互独立,彼此之间并不需要相互依赖才能发挥作用。
1.2 异步的用途
在计算机技术中,异步通常被用于两大领域
(1)异步通信
异步通信是一种在双方或多方之间交换消息的方式。其中每个参与方各自在他们方便或可操作的情况下接收并处理消息,而不是在收到消息后立即进行处理。 另外,消息的发送无需等待确认信息,前提是如果出现问题,接收方将请求更正或以其他方式处理该情况。
举个例子:电子邮件就是一种异步通信方式;发送者发送了一封邮件,接着接收者会在方便时读取和回复该邮件,而不是马上这样做。双方可以继续随时发送和接收信息,而无需双方安排何时进行操作。
(2)异步编程
在计算机编程中,异步操作意味着一个进程独立于其他进程运行,而同步操作意味着该进程仅作为某个其他进程完成或移交的结果而运行。换句话说,异步编程中一个任务与先前的一个(或多个)任务一起执行,而无需为了等待它们完成而停止执行。
对此,我一直有个疑问,JavaScript是单线程的,那又是如何实现异步的呢?答案是:虽然JavaScript是单线程的,但是浏览器是多线程的,具体可了解一下JavaScript在的事件循环、任务队列等知识。
1.3 同步和异步
(1)异步编程和同步编程
- 同步编程:它遵循一组严格的序列,这意味着操作一次执行一个,以完美的顺序执行。在执行一项操作时,会阻塞其他操作的指令。第一个任务的完成触发下一个任务,依此类推。
- 异步编程:它允许工作单元与主应用程序线程分开运行。当工作完成时,它会通知主线程(以及工作是完成还是失败) 简单来说:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
- 异步编程通过减少调用函数和返回该函数的值之间的延迟时间来增强用户体验。在现实世界中,这转化为更快、更无缝的流程。例如,用户希望他们的应用程序快速运行,但从应用程序编程接口 (API) 获取数据需要时间。在这些情况下,异步编程可以帮助应用程序屏幕加载更快,从而改善用户体验。
- 同步编程对开发人员有利。很简单,同步编程更容易编码。它在所有编程语言中都得到了很好的支持,并且作为默认的编程方法,开发人员不必花时间学习可能会打开错误之门的新东西。
(2)异步任务和同步任务
- 同步任务:是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
- 异步任务:是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了,该任务才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。
所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
(3)同步和异步的区别
异步和同步的区别包括:
- 异步是多线程的,这意味着操作或程序可以并行运行。同步是单线程的,因此一次只能运行一个操作或程序。
- 异步是非阻塞的,这意味着它将向服务器发送多个请求。同步是阻塞的——它一次只会向服务器发送一个请求,并等待服务器响应该请求。
- 异步增加了吞吐量,因为可以同时运行多个操作。同步更慢,更有条理。
:
个人之前一直误以为同步就是所有任务同时执行,而异步是所有任务不同时执行。但实际上并非如此,反而同步中的所有任务执行的时机是不同的,异步中的所有任务可能同时执行。在计算机编程中,个人认为同步和异步操作更加注重的是任务的执行顺序。例如上图:
:
这里抛出两个计算机中常见的概念,以防与同步和异步混淆:
- 并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
- 并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行
并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。
并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。
1.4 回调函数的缺点
回调函数的多层嵌套。随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,尤其是我们使用的是可能包含了很多循环和条件语句的真实代码。例如:
嵌套调用的“金字塔”随着每个异步行为会向右增长。很快它就失控了。所以,对此ES6引入了Promise和异步函数。
二、Promise
前面说了那么多,终于可以开始学习Promise了。我们先来看看 MDN 中对Promise的定义:
Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
一个 Promise 对象能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
一个 Promise 必然处于以下几种状态之一:
- 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled) : 意味着操作成功完成。(该状态有时候也称 resolved)
- 已拒绝(rejected) : 意味着操作失败。
待定状态(pending)的 Promise 对象要么会通过一个值被兑现(fulfilled) ,要么会通过一个原因(错误)被拒绝(rejected) 。
2.1 Promise 基本使用
(1)Promise() 构造器
Promise构造器主要用于包装不支持promise(返回值不是Promise)的函数。
语法:
new Promise(executor)
参数 executor:
executor是一个双参函数,创建新的Promise对象会立即执行executor。该executor是一段将输出与promise联系起来的自定义代码。executor通常定义如下:
function(resolutionFunc, rejectionFunc){
// 通常是一些异步操作
}
resolutionFunc与rejectionFunc也是函数,可以使用任何名字,接受任何类型的单个参数。
resolutionFunc(value) // 当被敲定时调用
rejectionFunc(reason) // 当被拒绝时调用(参数reason建议使用 Error 对象(或继承自 Error 的对象))
通常情况下会将这两个函数命名为:resolve 和 reject。
executor中的任一resolutionFunc与rejectionFunc被调用后,executor函数结束执行,并改变Promise对象的状态。- 该
executor的返回值将被忽略。 - 如果在该
executor中抛出一个错误,该promise将被拒绝。 executor参数一定要传递,否则会报错TypeError。
返回值
当通过new调用时,Promise构造函数返回一个promise对象。
executor中未调用resolutionFunc与rejectionFunc:返回一个pending状态的Promise对象
// 没有参数resolveFunc, rejectFunc,相当于未调用resolveFunc, rejectFunc
// let promise = new Promise(() => {
// })
let promise = new Promise((resolveFunc, rejectFunc) => {
})
console.log(promise) // Promise {<pending>}
- 当
resolutionFunc或者rejectionFunc被调用时,且resolutionFunc或者rejectionFunc参数不是Promise对象resolutionFunc被调用:返回一个fulfilled状态的Promise对象rejectionFunc被调用:返回一个rejected状态的Promise对象
// resolutionFunc被调用
let resolvedPromise = new Promise((resolveFunc, rejectFunc) => {
resolveFunc(1);
})
console.log(resolvedPromise) // Promise {<fulfilled>: 1}
// rejectionFunc被调用
let rejectedPromise = new Promise((resolveFunc, rejectFunc) => {
rejectFunc(1);
})
console.log(rejectedPromise) // Promise {<rejected>: 1}
- 当
resolutionFunc或者rejectionFunc被调用时,且resolutionFunc或者rejectionFunc参数是Promise对象resolutionFunc被调用:返回一个Promise对象,状态由传入的Promise`对象状态决定rejectionFunc被调用:返回一个rejected状态的Promise对象
let p = new Promise((resolveFunc, rejectFunc) => {
})
let resolvedPromise = new Promise((resolveFunc, rejectFunc) => {
resolveFunc(p);
})
console.log(resolvedPromise) // Promise {<pending>}
let rejectedPromise = new Promise((resolveFunc, rejectFunc) => {
rejectFunc(p);
})
console.log(rejectedPromise) // Promise {<rejected>: Promise}
(2)Promise对象的内部属性
- state —— 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"。
- result —— 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。(该属性是对应状态回调函数的参数值)
1. 初始 pending 状态
let promise = new Promise(function(resolve, reject) { })
2. fulfilled 状态
let promise = new Promise(function(resolve, reject) { resolve("success") })
3. reject 状态
let promise = new Promise(function(resolve, reject) { reject("error") })
(3)相关示例
- 未传递
executor
let promise = new Promise() // TypeError: Promise resolver undefined is not a function
executor只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。
let promise = new Promise(function(resolve, reject) {
resolve("1")
resolve("2")
reject("3")
})
console.log(promise) // Promise {<fulfilled>: '1'}
executor返回值被忽略
let promise = new Promise(function(resolve, reject) {
return 1
})
console.log(promise) // Promise {<pending>}
虽然返回值被忽略,但是executor中的return语句仍然会结束函数。
executor中抛出一个错误,该promise将被拒绝
let promise = new Promise(function(resolve, reject) {
throw new Error();
})
console.log(promise) // Promise {<rejected>: Error}
(4)小结
综上所述,通过Promise构造函数创建一个Promise对象的过程大致如下:
- 在构造函数生成新
Promise对象时,构造函数也生成了一对相关的函数resolutionFunc与rejectionFunc。他们被绑定在了Promise对象上 - 然后执行
executor内的一些操作(通常为异步操作),分别通过调用resolutionFunc或者rejectionFunc反应操作结果。 - 最后返回一个Promise对象,该对象的
state和result内部属性由executor内resolutionFunc或者rejectionFunc的被调用情况决定。
let promise = new Promise(function(resolve, reject) {
// 执行一些异步操作
......
if (......) {
resolve(value); // 接受
}else{
reject(reason); // 拒绝
}
})
2.2 Promise 原型上的方法
(1)Promise.prototype.then()
then() 方法返回一个 Promise 对象。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
语法
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
参数
- onFulfilled (可选) 当 Promise 变成接受状态(fulfilled)时调用的函数。
该函数有一个参数,即接受的最终结果(调用该then方法的Promise对象被接受的结果)。
如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
- onRejected (可选) 当 Promise 变成拒绝状态(rejected)时调用的函数。
该函数有一个参数,即拒绝的原因(调用该then方法的Promise对象被拒绝的原因)。
如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。
返回值
当一个 Promise 完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then 中的回调函数:
- 没有返回值:返回的Promise对象为接受状态,
result属性的值为undefined
let promise = new Promise(function(resolve, reject) {
resolve(1);
})
let promiseThen = promise.then((value) => {
})
console.log(promiseThen); // Promise{[[PromiseState]]: "fulfilled", [[PromiseResult]]: undefined}
- 返回了一个值:返回的Promise对象为接受状态,
result属性的值为返回的值
let promise = new Promise(function(resolve, reject) {
resolve(1);
})
let promiseThen = promise.then((value) => {
return 10;
})
console.log(promiseThen); // Promise{[[PromiseState]]: "fulfilled", [[PromiseResult]]: 10}
- 抛出了一个异常:返回的Promise对象为拒绝状态,
result属性的值为抛出的异常
let promise = new Promise(function(resolve, reject) {
resolve(1);
})
let promiseThen = promise.then((value) => {
throw new Error;
})
console.log(promiseThen); // Promise{[[PromiseState]]: "fulfilled", [[PromiseResult]]: Error at http://127.0.0.1:5500/index.js:6:11}
注意:不一定要显示抛出异常,代码运行过程中出现异常也会reject。
- 返回一个Promise对象:then的返回值即为该Promise对象
let promise = new Promise(function(resolve, reject) {
resolve(1);
})
// 返回一个接受状态的Promise对象
let promiseThen = promise.then((value) => {
return Promise.resolve(1);
})
console.log(promiseThen); // Promise{[[PromiseState]]: "fulfilled", [[PromiseResult]]: 1}
// 返回一个拒绝状态的Promise对象
let promiseThen = promise.then((value) => {
return Promise.reject(1);
})
console.log(promiseThen); // Promise{[[PromiseState]]: "rejected", [[PromiseResult]]: 1}
// 返回一个待定状态的Promise对象
let promiseThen = promise.then((value) => {
return new Promise(function() {});
})
console.log(promiseThen); // Promise{[[PromiseState]]: "pending", [[PromiseResult]]: undefined}
(2)Promise.prototype.catch()
catch() 方法返回一个Promise,并且处理拒绝的情况。
语法
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
参数:
onRejected
- 当Promise 被rejected时,被调用的一个Function。 该函数拥有一个参数:reason rejection 的原因。
- 如果 onRejected 抛出一个错误或返回一个本身失败的 Promise , 通过 catch() 返回的Promise 被rejected;否则,它将显示为成功(resolved)。
返回值
Promise对象,与then的返回值大致相同。
catch 与 then 的关系
在调用then方法时如果我们只对 error 进行处理,那么我们可以使用 null 作为第一个参数:.then(null, errorHandlingFunction)。
这种情况下,我们也可以直接使用 .catch(errorHandlingFunction)
let promise = new Promise(function(resolve, reject) {
reject('fail');
})
promise.then(null, (reason) => {
console.log('reject reason:', reason) // reject reason: fail
})
promise.catch((reason) => {
console.log('reject reason:', reason) // reject reason: fail
})
:以下代码中存在的区别
let promise = new Promise(function(resolve, reject) {
resolve('success');
})
// 以下两行代码并不等价
promise.then(f1).catch(f2);
promise.then(f1, f2);
promise.then(f1).catch(f2);中,如果f1中出现了错误,会被catch处理并执行f2promise.then(f1, f2);中只会执行f1,如果f1中出现了错误并没有相应的错误处理。- 如果
promise.then(f1, f2);改成promise.then(f1).then(null, f2);,那么就和promise.then(f1).catch(f2);等价了
(3)Promise.prototype.finally()
finally()方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
通常用于 promise 执行完毕后无论其结果怎样都做一些处理或清理。这样也避免了同样的代码在then()和catch()中重复编写。
语法
p.finally(onFinally);
p.finally(function() {
// 返回状态为(resolved 或 rejected)
});
参数
onFinally:Promise 结束后调用的Function。该函数没有参数
返回值
- 返回一个设置了 finally 回调函数的Promise对象。至于该Promise对象的状态,则由与finally最近的返回值决定。这个最近指的就是代码位置上离finally最近,且被调用了的
then()或者catch()方法的返回值。 - 如果抛出了一个异常,那么返回的Promise对象状态会称为被拒绝(rejected)
onFinally中的return语句会被忽略。
let promise = new Promise(function(resolve, reject) {
resolve('success');
})
let promiseFinally = promise.then((value) => {
return 1;
})
.catch((reason) => {
return 2;
})
.finally(() => {
return 3
})
console.log(promiseFinally) // Promise {[[PromiseState]]: "fulfilled", [[PromiseResult]]: 1}
finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
- 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
- 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
- 与Promise.resolve(2).then(() => {}, () => {}) (resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved的结果为 2。
- 同样,Promise.reject(3).then(() => {}, () => {}) (fulfilled的结果为undefined), Promise.reject(3).finally(() => {}) rejected 的结果为 3。
此外,还需要注意的一点是,Finally 并不意味着Promise的链式调用结束。Finally方法的返回值也是一个 Promise 对象。所以 Finally 之后仍然可以继续调用then()和catch()方法。
const myPromise =
(new Promise((resolve, reject) => {
resolve(1);
})
.finally(() => {
console.log('finally') // finally
})
.then((value) => {
console.log(value); // 1
}));
// finally
// 1
对于then()、catch()、finally()三个方法的参数可以采用箭头函数的形式来简化代码。
2.3 Promise的链式调用
回顾前面介绍的知识,我们知道:
- 创建promise时,它既不是成功也不是失败状态。这个状态叫作pending(待定)。
- 当promise返回时,称为 resolved(已解决).
- 一个成功resolved的promise称为fullfilled(实现)。它返回一个值,可以通过将
.then()块链接到promise链的末尾来访问该值。.then()块中的执行程序函数将包含promise的返回值。 - 一个不成功resolved的promise被称为rejected(拒绝)了。它返回一个原因(reason),一条错误消息,说明为什么拒绝promise。可以通过将
.catch()块链接到promise链的末尾来访问此原因。 2.2 中提到的Promise原型上的then()、catch()、finally()三个方法返回的都是Promise对象,所以它们可以被链式调用。
- 一个成功resolved的promise称为fullfilled(实现)。它返回一个值,可以通过将
常见的链式调用如下代码所示,这种方式让代码结构更加清晰,比起回调函数形式更易于阅读。
const myPromise =
(new Promise(myExecutorFunc))
.then(handleFulfilledA)
.then(handleFulfilledB)
.then(handleFulfilledC)
.catch(handleRejectedAny);
我们来看个例子:
onst myPromise =
(new Promise((resolve, reject) => {
console.log("新建Promise"); // 新建Promise
resolve(1);
})
.then((value) => {
console.log(value); // 1
return 2;
})
.then((value) => {
console.log(value); // 2
throw new Error(3);
})
.then((value) => {
console.log(value);
return 4;
})
.catch((reason) => {
console.log(reason); // Error: 3
return 5;
})
.finally(() => {
console.log('finally') // finally
}));
/*
新建Promise
1
2
Error: 3
at c:\Users\dali\Desktop\JavaScript基础知识\index.js:13:11
at processTicksAndRejections (internal/process/task_queues.js:95:5)
finally
*/
上述代码执行过程如下图所示,其中有一个then()方法未被调用,那是因为前面的操作中抛出了异常,Promise被拒绝,这时候会接交给catch()方法处理。此外,无论被接受还是拒绝,finally()函数都会被执行。
2.4 Promise 错误处理
(1)隐式 try……catch
前面提到,Promise构造函数参数executor中抛出一个异常,或者then()、catch()、finally方法的回调函数中抛出一个异常,都会返回一个被拒绝(rejected)状态的Promise对象,可以通过将.catch()块链接到promise链的末尾来访问此原因。
所以,在创建Promise或者Promise的链式调用中,如果发生异常就会被捕获,并被视为rejection进行处理
例如,以下两段代码等价:
new Promise((resolve, reject) => {
throw new Error("Whoops!"); // 抛出异常,视为rejection进行处理
}).catch(alert); // Error: Whoops!
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
- 我们有两种方式定义 error 处理程序,一种是通过
then()方法的第二个参数定义,另一种是通过catch()方法的参数来定义。
new Promise((resolve, reject) => {
reject('fail');
})
.catch((reason) => {
console.log(reason); // fail
});
new Promise((resolve, reject) => {
reject('fail');
})
.then(null, (reason) => {
console.log(reason); // fail
});
- 此外,无论是在
Promise构造函数或者then()、catch()、finally()等方法中抛出异常,控制权都会移交到后续最近的错误处理程序,中间若存在一些操作会被忽略。
(2)再次抛出(Rethrowing)—— catch中抛出异常
- 如果我们在
catch()中throw异常,那么控制权就会被移交到下一个最近的 error 处理程序(handler)。 - 如果我们处理该 error 并正常完成,那么它将继续到最近的成功的
then()处理程序(handler)。
const myPromise = new Promise((resolve, reject) => {
throw new Error("Whoops!");
})
.then(() => {
console("不会执行")
})
.catch((error) => {
if (error instanceof URIError) {
// 处理错误
} else {
console.log("Can't handle such error");
throw error; // 再次抛出此 error 或另外一个 error,执行将跳转至下一个 catch
}
})
.then(() => { // 因为前面地catch在没有解决错误,所以这里跳过
console.log("不会执行")
})
.catch(error => {
console.log(`The unknown error has occurred: ${error}`);
})
.then(() => {
console.log("这里会执行")
})
.finally(() => {
console.log("收尾!")
});
// 程序输出如下:
// Can't handle such error
// The unknown error has occurred: Error: Whoops!
// 这里会执行
// 收尾!
(3)未处理的 rejection
对于在 promise 中未被处理的 rejection,JavaScript 引擎会跟踪此类 rejection,在这种情况下会生成一个全局的 error。例如,下述代码中存在未被处理的 rejection ,抛出了一个全局的错误。
// Uncaught (in promise) fail
let promise = new Promise(function(resolve, reject) {
reject('fail');
})
// Uncaught (in promise) Error: fail
let promise = new Promise(function(resolve, reject) {
throw new Error('fail');
})
如果出现了未处理的 rejection ,就会触发 unhandledrejection 处理程序,并获取具有 error 相关信息的 event 对象。
此外,这类 error 通常是无法恢复的,所以我们最好的解决方案是将问题告知用户,并且可以将事件报告给服务器
window.addEventListener('unhandledrejection', function(event) {
// 这个事件对象有两个特殊的属性:
console.log(event.promise); // Promise {<rejected>: Error: Whoops!} - 生成该全局 error 的 promise
console.log(event.reason); // Error: Whoops! - 未处理的 error 对象
});
// new Promise(function() {
// throw new Error("Whoops!");
// }); // 没有用来处理 error 的 catch
new Promise(function(resolve, reject) {
reject(new Error("Whoops!"));
}); // 没有用来处理 error 的 catch
unhandledrejection事件触发的原因:
当微任务队列中的任务都完成时,才会生成 unhandledrejection:引擎会检查 promise,如果 promise 中的任意一个出现 “rejected” 状态,unhandledrejection 事件就会被触发。
2.5 Promise 对象自身的方法
为了更好更优雅地进行异步处理,Promise自身也提供了一些方便操作地方法。
(1)Promise.resolve()
Promise.resolve(value)方法返回一个以给定值解析后的 Promise 对象
语法
Promise.resolve(value);
参数
value:将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
返回值
返回一个带着给定值解析过的Promise对象。
- 如果
value不是 Promise 对象,那么返回一个以此值完成的 Promise 对象。
console.log(Promise.resolve()) // Promise {<fulfilled>: undefined}
console.log(Promise.resolve(1)); // Promise {<fulfilled>: 1}
console.log(Promise.resolve('success')); // Promise {<fulfilled>: 'success'}
console.log(Promise.resolve([1, 2])); // Promise {<fulfilled>: Array(2)}
- 如果
value是 Promise 对象,那么返回这个 Promise 对象
// 待定状态的Promise对象
let promise = new Promise((resolve, reject) => {
})
console.log(Promise.resolve(promise)); // Promise {<pending>}
// 完成状态的Promise对象
let promise = new Promise((resolve, reject) => {
resolve(1);
})
console.log(Promise.resolve(promise)); // Promise {<fulfilled>: 1}
// 拒绝状态的Promise对象
let promise = new Promise((resolve, reject) => {
reject(1);
})
console.log(Promise.resolve(promise)); // Promise {<rejected>: 1}
- 如果
value是 thenable(即带有then方法的对象),返回的promise会“跟随”这个thenable的对象,采用它的最终状态
var p = Promise.resolve({
then: function(onFulfill, onReject) {}
});
console.log(p) // Promise {<pending>, undefined}
var p = Promise.resolve({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p) // Promise {<fulfilled>, fulfilled}
var p = Promise.resolve({
then: function(onFulfill, onReject) { onReject("rejected!"); }
});
console.log(p) // Promise {<rejected>, rejected}
// then 方法中抛出异常
var p = Promise.resolve({
then: function(onFulfill, onReject) {
throw new Error('fail');
}
});
console.log(p) // Promise {<rejected>, Error: fail}
手动实现
function _resolve(value) {
// value 是一个 Promise 对象,直接返回
if (value instanceof Promise) {
return value;
}
// value 不是 Promise 对象,则新建一个Promise对象并返回
return new Promise((resolve, reject) => {
// 如果 value 是一个 thenable
if (value && value.then && typeof(value) === 'function') {
value.then(resolve, reject);
} else { // value 是其他普通值
resolve(value);
}
})
}
(2)Promise.reject()
Promise.reject()方法返回一个带有拒绝原因的Promise对象。
语法
Promise.reject(reason);
参数
reason:表示Promise被拒绝的原因。(一般选择Error实例对调试和选择性错误捕捉很有帮助)
返回值
一个给定原因了的被拒绝的 Promise 对象。无论传递的参数是什么类型,返回的Promise对象都是被拒绝的,哪怕传递的参数是一个被接受的Promise对象。参数为thenable也不会进行相应处理,只会将该thenable作为拒绝的理由。
console.log(Promise.reject()) // Promise {<rejected>: undefined}
console.log(Promise.reject(1)) // Promise {<rejected>: 1}
console.log(Promise.reject('fail')) // Promise {<rejected>: 'fail'}
console.log(Promise.reject(Promise.resolve('success'))) // Promise {<rejected>: Promise}
var p = Promise.reject({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p) // Promise {<rejected>: {…}}
手动实现
function _reject(reason) {
// 以 reason 拒绝 Promise
return new Promise((resolve, reject) => {
reject(reason);
})
}
(3)Promise.all()
Promise.all() 方法接收一个promise的iterable类型的输入,并且只返回一个
Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。
语法
Promise.all(iterable);
参数
iterable:一个可迭代对象,若未传递改参数,会抛出TypeError异常
返回值
完成(Fulfillment):
- 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved) 状态的
Promise。(这里是同步的,返回的Promise对象状态立即变为完成)
// 空数组
console.log(Promise.all([])); // Promise {<fulfilled>: Array(0)}
// 空字符串
console.log(Promise.all('')); // Promise {<fulfilled>: Array(0)}
// 空Map
console.log(Promise.all(new Map())); // Promise {<fulfilled>: Array(0)}
// 空集合
console.log(Promise.all(new Set())); // Promise {<fulfilled>: Array(0)}
- 如果传入的参数不包含任何
promise,则返回一个异步完成(asynchronously resolved)Promise。
p = Promise.all([1, 2])
// 返回的Promise对象异步完成,即没有立即变为完成状态
console.log(p); // Promise {<pending>}
// 异步变为完成状态
setTimeout(function(){
console.log(p); // Promise {<fulfilled>: Array(2)}
});
- 如果所有传入的
promise都变为完成状态,Promise.all返回的promise异步地变为完成。
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
var p = Promise.all(resolvedPromisesArray);
console.log(p); // 这里可以看出,返回的Promise并不立即变为完成状态
setTimeout(function(){
console.log('the stack is now empty');
console.log(p);
});
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
- 在任何情况下,
Promise.all返回的promise的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非promise值)。返回值将会按照参数内的promise顺序排列,而不是由调用promise的完成顺序决定。 失败/拒绝(Rejection): - 如果传入的
promise中有一个失败(rejected),Promise.all异步地将失败的那个结果给失败状态的回调函数,而不管其它promise是否完成。返回的promise异步地变为拒绝。
var mixedPromisesArray = [Promise.resolve(33), Promise.reject(44)];
var p = Promise.all(mixedPromisesArray);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<rejected>: 44}
});
手动实现
function _all(iterable) {
// 判断传入的参数是否可迭代
if (!iterable || !iterable[Symbol.iterator]) {
throw new TypeError('object is not iterable')
}
const count = 0;
const result = []; // // 记录完成的结果
// 返回一个Promise对象
return new Promise((resolve, reject) => {
const len = iterable.length // 集合长度
// 可迭代对象为空
if (len === 0)
resolve(result);
for (let i = 0; i < len; i++) {
// 如果是一个Promise对象
Promise.resolve(iterable[i])
.then((value) => {
result[i] = value;
count ++;
// 如果所有promise都完成
if (count === len)
resolve(result);
})
.catch((reason) => {
reject(reason);
})
}
})
}
(4)Promise.allSettled()
该
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
语法
Promise.allSettled(iterable);
参数
iterable:一个可迭代对象,其中每个成员都是Promise。
返回值
- 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved) 状态的
Promise。
var p = Promise.allSettled([]);
console.log(p); // Promise {<fulfilled>: Array(0)}
- 如果promises 集合中存在待定状态(pending)的promise,那么返回一个待定状态的
Promise
let p1 = new Promise((resolve, reject) => {
resolve(1);
})
let p2 = new Promise((resolve, reject) => {
reject(2);
})
let p3 = new Promise((resolve, reject) => {
})
var p = Promise.allSettled([p1, p2, p3]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<pending>}
});
- 一旦所指定的 promises 集合中每一个 promise 都已经完成,无论是成功的达成或被拒绝,
Promise将被异步完成。所返回的 promise 的处理器将传入一个数组作为输入,该数组包含原始 promises 集中每个 promise 的结果。对于每个结果对象,都有一个status字符串。- 如果值为
fulfilled,则结果对象上存在一个value。 - 如果值为
rejected,则存在一个reason。 value(或 reason )反映了每个 promise 决议(或拒绝)的值。
- 如果值为
let p1 = new Promise((resolve, reject) => {
resolve(1);
})
let p2 = new Promise((resolve, reject) => {
reject(2);
})
let p3 = new Promise((resolve, reject) => {
resolve(1);
})
var p = Promise.allSettled([p1, p2, p3]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<rejected>: 44}
});
- 此外,如果传入的 promises 集合中包含非Promise对象,会被自动转换成已完成的状态的Promise对象
var p = Promise.allSettled([Promise.resolve(1), Promise.reject(2), 3, 'success']);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<fulfilled>: Array(4)}
});
手动实现
function _allSettled(iterable) {
// 判断传入的参数是否可迭代
if (!iterable || !iterable[Symbol.iterator]) {
throw new TypeError('object is not iterable')
}
// 返回一个Promise对象
return new Promise((resolve, reject) => {
let result = []; // 记录完成的结果
const len = iterable.length // 集合长度
// 集合为空
if (len === 0)
resolve(result);
for (let i = 0; i < len; i++) {
// 如果是一个Promise对象
Promise.resolve(iterable[i])
.then((value) => {
result.push({status: 'fulfilled', value});
})
.catch((reason) => {
result.push({status: 'rejected', reason});
})
.finally(() => {
// 如果所有promise都完成
if (i === (len - 1))
resolve(result);
})
}
})
}
(5)Promise.any()
Promise.any()接收一个Promise可迭代对象,只要其中的一个promise成功,就返回那个已经成功的promise。如果可迭代对象中没有一个promise成功,就返回一个失败的promise和AggregateError类型的实例,它是Error的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
语法
Promise.any(iterable);
参数
iterable:一个可迭代对象。
返回值
成功(fulfillment):
- 如果传入的参数不包含任何
promise,则返回`一个 异步完成 (asynchronously resolved)的 Promise。且完成的结果为第一个迭代元素。
var p = Promise.any([1, 2]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<fulfilled>: 1}
});
- 只要传入的迭代对象中的任何一个
promise变成成功(resolve)状态,那么返回的promise就会 异步地(当调用栈为空时)变成成功(resolved)状态。 拒绝(rejection): - 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
console.log(Promise.any([]))
// romise {<rejected>: AggregateError: All promises were rejected}
- 如果所有传入的
promises都失败,Promise.any将返回异步失败,和一个AggregateError对象,它继承自Error,有一个error属性,属性值是由所有失败值填充的数组。
var p = Promise.any([Promise.reject(1), Promise.reject(2)]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<rejected>: AggregateError: All promises were rejected}
});
手动实现
function _any(iterable) {
// 判断传入的参数是否可迭代
if (!iterable || !iterable[Symbol.iterator]) {
throw new TypeError('object is not iterable')
}
// 返回一个Promise对象
return new Promise((resolve, reject) => {
let result = []; // 记录完成的结果
const len = iterable.length // 集合长度
// 集合为空
if (len === 0)
reject(new Error(result));
for (let i = 0; i < len; i++) {
// 如果是一个Promise对象
Promise.resolve(iterable[i])
.then((value) => {
resolve(value);
})
.catch((reason) => {
result.push(reason);
// 如果所有promise都被拒绝
if (i === (len - 1))
reject(new Error(result))
})
}
})
}
这里的手动实现只是一个大体思路,与原生的方法之间仍然存在很大差别。
(6)Promise.race()
Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
语法
Promise.race(iterable);
参数
iterable:一个可迭代对象。
返回值
一个待定的 Promise只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的值,从而异步地解析或拒绝(一旦堆栈为空)。
race函数返回一个Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
var p = Promise.race([]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<fulfilled>: 33}
});
var resolvedPromisesArray = [ Promise.reject(44), Promise.resolve(33)];
var p = Promise.race(resolvedPromisesArray);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<rejected>: 44}
});
- 如果传的迭代是空的,则返回的 promise 将永远等待。
var p = Promise.race([]);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<pending>}
});
- 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则
Promise.race将解析为迭代中找到的第一个值。
var resolvedPromisesArray = [1, Promise.reject(44), Promise.resolve(33)];
var p = Promise.race(resolvedPromisesArray);
console.log(p); // Promise {<pending>}
setTimeout(function(){
console.log(p); // Promise {<fulfilled>: 1}
});
再来看MDN上的一个例子:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// 两个都完成,但 p2 更快
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then(function(value) {
console.log(value); // "three"
// p3 更快,所以它完成了
}, function(reason) {
// 未被调用
});
var p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then(function(value) {
// 未被调用
}, function(reason) {
console.log(reason); // "six"
// p6 更快,所以它失败了
});
手动实现
function _race(iterable) {
// 判断传入的参数是否可迭代
if (!iterable || !iterable[Symbol.iterator]) {
throw new TypeError('object is not iterable')
}
// 返回一个Promise对象
return new Promise((resolve, reject) => {
// 集合为空,仍然处于待定状态
if (iterable.length === 0)
return;
for (let element of iterable) {
Promise.resolve(element)
.then((value) => {
resolve(value);
})
.catch((reason) => {
reject(reason);
})
}
})
}
小结
- 有关
Promise.all()、Promise.allSettled()、Promise.any()、Promise.race()四个方法有一个共同的特点,即:当传入的可迭代对象包含非Promise对象时,会通过类似于Promise.resolve()类似的方法将其转换为一个Promise对象,然后再进行处理。之所以这么说,我们可以看看当我们传递的可迭代对象中包含thenable时的结果:
let t1 = {
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
};
let t2 = {
then: function(onFulfill, onReject) { onReject("rejected!"); }
};
let p1 = Promise.all([t1, t2]);
setTimeout(function(){
console.log(p1); // Promise {<rejected>: 'rejected!'}
});
let p2 = Promise.allSettled([t1, t2]);
setTimeout(function(){
console.log(p2); // Promise {<fulfilled>: Array(2)}
});
let p3 = Promise.any([t1, t2]);
setTimeout(function(){
console.log(p3); // Promise {<fulfilled>: 'fulfilled!'}
});
let p4 = Promise.race([t1, t2]);
setTimeout(function(){
console.log(p4); // Promise {<fulfilled>: 'fulfilled!'}
});
由上述代码可见,所有thenable会按Promise.resolve()的规则转换成Promise对象。
- 上述手动实现的代码中或多或少与原生方法的执行机制存在区别,仅供参考。
2.6 小结
有关Promise的知识就介绍到这了,以上也是阅读官方文档的一些记录。那么东西看起来确实很乏味,那我们就换一种思路区看待Promise以及Promise的链式调用。讲个小故事:
假设我们把Promise中要做的异步操作是"张三向暗恋已久的女神表白",然后resolve和reject是女神对待张三这次表白的态度。
如果张三用真心打动了女神,女神接受了张三的表白,那么她会resolve("我愿意"),张三知道女神接受了表白,那么然后张三就会开始和女神一起逛街、看电影等等呀,那么这就是then(() => {逛街、看电影}),当然在和女神相处的时候女神也会告诉张三他喜欢啥是吧,那么然后张三就回去给她买,那么这就是then(value) => {买礼物}。
反之,女神拒绝了张三表白,告诉你为什么会拒绝的原因,她会reject("你是个好人")。知道了原因后,张三痛下决心,要努力改变自己,既然女神说我是个好人,那么我就努力变坏,这就是catch((reason) => {努力变坏})(开个玩笑,可不能变坏哟!)。另外,就算女神接受了,在与女神相处的过程中,总会有矛盾吧(除非张三是个深情的人,哈哈)。出现了矛盾,这就相当于then(() => {throw 矛盾}),出现了矛盾就要解决是吧,那么就是catch((矛盾) => {解决矛盾}),矛盾解决了,那就接着好好相处嘛...
最后,张三是个积极向上的人,这次表白是被接受还是拒绝,张三都会继续努力提升自己、积极生活,凡是总要向前看嘛。这就是finally(() => {努力提升自己,积极生活})。
以上故事仅供休闲,与Promise机制还是有所差异的。
三、async/await
3.1 await 操作符
await操作符用于等待一个Promise对象。它只能在异步函async function中使用。
await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。
- 若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行
async function。 - 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
语法:
[返回值] = await 表达式;
-
表达式:一个
Promise对象或者任何要等待的值。 -
返回值:返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
3.2 异步函数
(1)AsyncFunction
AsyncFunction构造函数用来创建新的异步函数 对象,JavaScript 中每个异步函数都是AsyncFunction的对象。
注意,AsyncFunction 并不是一个全局对象
new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)
(2)async 关键字
async函数是使用
async关键字声明的函数。async关键字可用于函数声明、函数表达式以及箭头函数上。
async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
语法
async function name([param[, param[, ... param]]]) {
statements
}
返回值
- 一个
Promise,这个promise要么会通过一个由async函数返回的值被解决,要么会通过一个从async函数中抛出的(或其中没有被捕获到的)异常被拒绝。
// 被拒绝
async function foo() {
throw new Error();
}
console.log(foo());
// 被解决
async function bar() {
return Promise.resolve();
}
console.log(bar());
- async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。
// 两个函数等价
async function foo() {
return 1
}
function foo() {
return Promise.resolve(1)
}
async函数的执行机制
async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。
在await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。
例如:
// 以下两端代码等价
async function foo() {
await 1
}
function foo() {
return Promise.resolve(1).then(() => undefined)
}
async函数中使用try/catch进行错误处理
async function f() {
try {
var z = await Promise.reject(30);
} catch (e) {
console.log(e); // 30
}
}
f();
本文到这里就结束啦,以上内容是个人在学习过程中的一些记录,都是基于官方文档和大佬的肩膀的。有关JavaScript中如何进行优雅的异步处理,后续会继续学习。写的也比较仓促,有问题希望大佬们可以提出指正。
参考
[1] Promise
[2] async函数
[3] await
[4] 《JavaScript高级程序设计(第四版)》