JavaScript引擎是基于单线程事件循环的概念构建的,它采用任务队列的方式,将要执行的代码块放到队列中,当JavaScript引擎中的一段代码执行结束,事件循环会指定队列中的下一个任务来执行。事件循环是JavaScript引擎中的一段程序,负责监控代码执行并管理任务队列。
JavaScript执行异步调用的传统方式是事件的回调函数,但随着应用的复杂,事件和回调函数无法满足开发者想做的事情,因此,ECMAScript 6给出了promise这一更强大的异步编程解决方案。
一个promise可以通过promise构造函数来创建,这个构造函数值只接受一个参数:包含初始化promise代码的执行器(executor)函数,在该函数内包含需要异步执行的代码。执行器函数接受两个参数,分别是resolve函数和reject函数,这两个函数由JavaScript引擎提供,不需要我们自己编写。异步操作结束成功时调用resolve函数,失败时调用reject函数。
代码示例如下
const promise = new promise(function(resolve, reject) {
// 开启异步操作
setTimeout(function() {
try {
let c = 6 / 2
// 执行成功调用resolve函数
resolve(c)
} catch(e) {
// 执行失败调用reject函数
reject(e)
}
}, 1000)
})
在执行器函数内部包含了异步调用,在1s后执行两个数的除法运算,如果成功,则用相除的结果作为参数调用resolve函数,失败则调用reject函数。
每个promise都会经历一个短暂的生命周期:先是处于进行中(pending)的状态,此时操作尚未完成,所以它也是未处理的(unsettled),一旦异步操作执行结束,promise则变为已处理的(settled)状态。操纵结束后,根据异步操作执行成功与否,可以进入以下两个状态之一:
- fulfilled:promise异步操作成功完成。
- rejected:由于程序错误或者其他一些原因,promise异步操作未能成功完成,即已失败。
一旦promise状态改变,就不会再变,任何时候都可以得到这个结果。promise对象的状态改变只有两种可能:
- 从pending变为fulfilled
- 从pending变为rejected
在promise状态改变后,我们怎么去根据不同的状态来做相应的处理呢?
promise对象有一个then()方法,它接受两个参数:
- 第一个是当promise的状态变为fulfilled时要调用的函数,与异步操作相关的附加数据通过调用resolve函数传递给这个完成函数
- 第二个是当promise的状态变为rejected时要调用的函数,所有与失败相关的附加数据通过调用reject函数传递给这个拒绝函数。
添加上述promise的then()方法的调用
promise.then(function(value) {
// 完成, 输出3
console.log(value)
}, function(err) {
// 拒绝
console.error(err.message)
})
then()方法的两个参数都是可选的。
只在执行失败后进行处理
// 给then()方法的第一个参数传递null
promise.then(null, function(err) {
// 拒绝
console.error(err.message)
})
promise对象还有一个catch()方法,用于在执行失败后进行处理,等价于上述then()方法。
使用catch()方法
promise.catch(function(err) {
console.error(err.message)
})
通常是将then()方法和catch()方法一起使用来对异步操作的结果进行处理,这样能更清晰地指明操作结果是成功还是失败。
同时使用then()方法和catch()方法
promise.then(function(value) {
// 完成
console.log(value)
}).catch(function(err) {
// 拒绝
console.error(err.message)
})
使用箭头函数书写
promise.then(value => console.log(value))
.catch(err => console.error(err.message))
- 修改上述promise,将除数改为0,在Node中运行代码,结果为Infinity
- 如果调用resolve函数或reject函数时带有参数,那么它们的参数会被传递给then()或catch()方法的回调函数
promise支持方法链的调用形式,如上述代码所示,每次调用then()或者catch()方法 时实际上会创建并返回另一个promise,因此可以将promise串联调用。串联调用时,只有在前一个promise完成或被拒绝时,第二个才会被调用。
代码示例如下
const promise = new promise((resolve, reject) => {
// 调用setTimeout模拟异步操作
setTimeout(() => {
let intArray = new Array(20)
for (let i = 0; i < 20; i++) {
intArray[i] = parseInt(Math.random() * 20, 10)
}
// 成功后调用resolve
resolve(intArray)
}, 1000)
// 该代码会立即执行
console.log("开始生成一个随机数的数组")
})
promise.then(value => {
value.sort((a, b) => a-b)
return value
}).then(value => console.log(value))
需要说明的是:
- promise的执行器函数内的代码会立即执行,因此无论setTimeout指定的回调函数执行成功与否,console.log()语句都会执行
- 在20个随机数生成完毕后,调用resolve(intArray),因而then()方法的完成处理函数被调用,对数组进行排序,之后返回value;接着下一个then()方法的完成处理函数开始调用,输出排序后的数组。
- promise链式调用时,有一个重要特性就是可以给后续的promise传递数据,只需要在完成处理函数中指定一个返回值(如上述代码中的return value)就可以沿着promise链继续传递数据。
在完成处理程序或拒绝处理程序中也可能会产生错误,使用promise链式调用可以很好地捕获这些错误。
代码示例如下
const promise = new promise((resolve, reject) => {
resolve("Hello World")
})
promise.then((value) => {
console.log(value)
throw new Error("错误")
}).catch(err => console.error(err.message))
要注意的是,与JavaScript中的try/catch代码块不同,如果没有使用catch()方法指定错误处理的回调函数,那么promise对象抛出的错误不会传递到外层代码,即不会有任何反应。