前言
JavaScript天生就是支持异步编程的,但是最开始的异步编程方法是事件模型和回调函数,这两种方式在面对复杂情况时显得力不从心,而Promise
就是异步编程的一种解决方法。
异步编程的背景知识
由于Js引擎是基于单线程事件循环的概念构建的,所以同一时刻,只允许一个代码块执行。
任务队列(job queue):每当一段代码将要运行时就会被添加到任务队列中。
事件循环(event loop):当一段代码执行完,会执行任务队列中的下一个任务。
事件模型
let button = documnet.getElementById('btn')
button.onclick = function(event){
console.log('clicked')
}
复制代码
这是JavaScript中最基础的异步编程形式,直到事件触发时才执行事件处理程序。 事件模型适用于响应用户交互和完成类似的低频功能。
回调模式
回调模式与事件模型类似,异步代码都会在未来的某个时间点执行。
readFile("example.txt", function(err, contents) {
if(err) {
throw err;
}
console.log(contents)
})
console.log('Hi');
复制代码
- 分析这段代码:由于使用了回调模式,
readFile
函数会立即执行,当读取磁盘上文件时,暂停执行,交出 Js 引擎线程,console.log('Hi')
会立即执行。当readFile
结束执行时,会向任务队列的末尾添加一个新任务,该任务包含回调函数和相应的参数(就是闭包咯),待队列前执行完,执行该任务。
回调模式如果嵌套太多的回调函数,会导致代码难以理解,使自己陷入回调地狱。
Promise的基础知识
Promise相当于是异步操作的占位符,会让函数返回一个表示异步操作的Promise对象,未来对这个对象的操作取决于Promise的生命周期。
Promise的生命周期
- 进行中(pending)
- 已处理(settled)
- 成功(fulfilled)
- 未成功(rejected)
所有的 promise 对象都要实现then()
方法,接受两个参数:第一个是当promise
的状态变为 fulfilled
时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数;第二个是 promise
状态变为 rejected
时要调用的函数。
Promise 对象还有一个 catch()
方法,相当于只传第二个参数的 then
。
一个对象如果实现了then方法,就称为
thenable
对象,所有的 promise 对象都是 thenable 对象,但 thenable 对象不一定是 promise 对象。
创建未完成的 Promise
用 Promise
的构造函数可以创建新的 Promise 。构造函数只接受一个参数:包含初始化Promise代码的执行器(executor)函数。执行器函数有两个参数,分别是·esolve
函数和reject
函数,执行器成功完成时调用 resolve 函数,失败时调用 reject 函数。
let promise = new Promise(resolve, reject) {
console.log('promise执行器');
resolve();
}
promise.then(function() {
console.log('Resolved')
})
console.log('hi')
// 执行结果
// promise执行器
// hi
// Resolved
复制代码
- 调用
resolve
后会触发一个异步操作,将传入then
和catch
中的方法加入任务队列中,并异步执行。
任务编排(job scheduling):当编排任务时,会向任务队列中添加一个新任务,并明确指定将任务延后执行。类似setTimeout。
resolve()
和reject()
:定义在 then 或 catch 中,执行在 promise 构造函数。
创建已处理的 Promise
- 用
Promise.resolve()
或Promise.reject()
创建完成态 / 已拒绝的 Promise。也就是说不会有任务编排的过程,而且需要需要向 Pormise 添加一至多个完成处理程序(then/catch 的参数)来获取值。
let promsie = Pormise.resolve(42);
promise.then(function(value) {
console.log(value) // 42
}
复制代码
全局的 Promise 拒绝处理
Pormise 的一个问题是,任何时候都可以调用 then 方法和 catch 方法,无论 Promise 是否已解决这两个方法都可以正常运行。
- 在Node.js的环境中
在Node.js中处理promise拒绝会触发process对象上的两个事件:
unhandledRejection
:在一个事件循环中,当Promise被拒绝,且没有提供拒绝处理程序时,触发该事件。rejectionHandled
:在一个事件循环后,当Promise被拒绝时,若拒绝处理程序被调用,触发该事件。
- 在浏览器环境
浏览器也会触发上述两个事件,且这些事件绑定在 window 对象上。
串联 Promise
Pormise 每次调用 then()
方法或 catch()
方法时,实际上创建并返回了另一个 Promise。也就是promise的then可以链式调用。而且只有当前一个执行完了才可以执行下一个。
let promise = new Promise(function(resolve,reject) {
resolve(42)
})
promise.then(function(value) {
console.log(42)
throw new Error('Boom!')
}).catch(function(err){
console.log(err.message)
})
复制代码
- 在 promise 链中,返回值可以用在下一个 Pormise 的完成处理函数中。
- 在 Promise 链中,返回 Promise 对象。(原本的then就是返回一个promise对象,这里就是显式指定了一下)
let p1 = new Promise(function(resolve, reject) {
resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
resolve(43)
})
p1.then(function(value) {
console.log(value) // 42
return p2
}).then(function(value) {
console.log(value) // 43
})
复制代码
响应多个 Promise
Promise.all()
:接受一个参数并返回一个 Promise,参数是一个含有多个受监视 Promise 的可迭代对象(例如一个数组),只有当可迭代对象中所有 Promise 都被解决后返回的 Promise 才会被解决。传入Promise.all()
Promise 中,只要有一个被拒绝,那么返回的 Promise 不等所有 Promise 都完成就立即被拒绝。
let p1 = new Promise(function(resolve, reject) {
resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
resolve(43)
})
let p3 = new Promise(function(resolve, reject) {
resolve(44)
})
let p4 = Promise.all([p1, p2, p3])
p4.then(function(value) {
console.log(value.isArray()) // true
console.log(value[0]); // 42
console.log(value[1]); // 43
console.log(value[2]); // 44
})
复制代码
Promise.race()
:只要有一个 Promise 被解决,返回的promise就被解决。实际上,传入该方法的 Promise 会进行竞争,哪个先完成,就返回哪个。
let p1 = new Promise(function(resolve, reject) {
resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
resolve(43)
})
let p3 = new Promise(function(resolve, reject) {
resolve(44)
})
let p4 = Promise.race([p1, p2, p3])
p4.then(function(value){
console.log(value.isArray()) // false
console.log(value) // 42
})
复制代码
异步编程更优解
async
和await
用
async
代替函数生成器,用await
代替 yield 来调度函数。asyn
关键字表示函数以异步模式运行,await
关键字表示调用的函数应该返回一个 Promise。
参考文献