前言
JS 引擎是单线程的,同一时刻只能执行一个代码块,所以有一个任务队列来存放即将运行代码。JS引擎中的事件循环程序用于执行队列中下一个任务,负责监控代码执行和管理任务队列。
Promise
当代码中涉及嵌套多个方法调用时,就会使自己陷入回调地狱。
Promise 能解决回调地狱的问题,帮助我们更好跟踪多个回调函数并理清程序操作。
Promise会经历一个生命周期:先是处于进行中(pending)状态,操作完成状态变成已处理(settled)。
已处理状态有两个:
- Fulfilled,表示 Promise 已完成
- Rejected,表示 Promise 已拒绝,即 Promise 异步操作失败
所以Promise的状态有3种:"pending"、"fulfilled"、"rejected"。
创建 Promise
创建 Promise 的两种方式:
- 构造函数
- Promise 静态方法
let value = 200;
// 构造函数方式
let p1 = new Promise( (resolve, reject) => {
setTimeout(() => resolve(value), 1000);
});
//静态方法
let p2 = Promise.resolve(value);
let p3 = Promise.reject(new Error ("Explosion!"));
console.log(p1 instanceof Promise); // true
console.log(p2 instanceof Promise); // true
console.log(p3 instanceof Promise); // true
上面示例中创建了3种状态的 Promise:
- 构造函数所创建的 Promise 为未解决(pending)状态。
- Promise.resolve() 创建的 Promise 为已完成(fulfilled)状态。
- Promise.reject() 创建的 Promise 为已拒绝(rejected)状态。
then()
每个 Promise 都有 then() 方法,接受两个参数:第一个是当 Promise 完成时所调用的函数(已完成函数),异步操作完成所附加的数据会传递给这个函数;第二个当 Promise 拒绝时所调用的函数(已拒绝函数),程序失败的错误数据会传递给这个函数。
function fun (value, flag) {
return new Promise( (resolve, reject) => {
setTimeout(() => {
if(flag) {
resolve(value);
} else {
reject(new Error("Explosion!"));
}
}, 1000);
})
}
let p1 = fun("已完成", true);
let p2 = fun("已拒绝", false);
p1.then( value => console.log(value), error => console.log(error.message) );
// "已完成"
p2.then( value => console.log(value), error => console.log(error.message) );
// "Explosion!"
catch()
catch()等价于只传入已拒绝处理程序的then()。
let promise = Promise.reject(new Error ("Explosion!"));
promise.catch(error => console.log(error.message));
// 等价于
promise.then(null, error => console.log(error.message));
全局 Promise 拒绝处理
node.js环境
在node.js中,处理 Promise 拒绝时会触发 process 对象上的两个事件:
- unhandledRejection 在一个事件循环中,当 Promise 已拒绝状态,并且没有相应的拒绝程序处理,则触发该事件。
- rejectionHandled 在一个事件循环中,当 Promise 已拒绝状态,并且拒绝程序被调用,则触发该事件。
// unhandledRejection 事件
let reject = Promise.reject(new Error("explosion"));
process.on("unhandledRejection", function (error, promise){
console.log(reason.message); // "explosion"
console.log(promise === reject); // true
});
// rejectionHandled 事件
process.on("rejectionHandled", function (promise){
console.log(promise === reject); // true
});
// 2秒后调用 catch()
setTimeout(() => reject.catch(() => console.log("拒绝程序调用!") )), 2000;
浏览器环境
浏览器在 window 对象上也有两个触发处理 Promise 拒绝时的事件:
- unhandledrejection 与 node.js 环境的 unhandledRejection 触发条件一致。
- rejectionhandled 与 node.js 环境的 rejectionHandled 触发条件一致。
let reject = Promise.reject(new Error ("explosion"));
window.onunhandledrejection = function (event) {
console.log(event.type); // "unhandledrejection"
console.log(event.promise === reject); // true
console.log(event.reason.message); // "explosion"
};
window.onrejectionhandled = function (event) {
console.log(event.type); // "rejectionhandled"
console.log(event.promise === reject); // true
console.log(event.reason.message); // "explosion"
};
// 2秒后调用 catch()
setTimeout(() => reject.catch(() => console.log("拒绝程序调用!") )), 2000;
/*
如果在创建 reject 之后直接添加拒绝处理程序,
那么 unhandledrejection 和 rejectionhandled 事件将不被触发,
因为创建 reject 和添加拒绝程序在同一个事件循环中,
所以使用setTimeout()方法添加拒绝处理程序。
同样 node.js 环境下的触发拒绝事件也是。
*/
监听多个 Promise
Promise.all()
Promise.all()方法只接受一个参数并返回一个 Promise,该参数是一个包含多个 Promise 的迭代对象。当所有 Promise 被解决返回的 Promise 才被解决,当所有 Promise 被完成返回的 Promise 才变已完成状态。
let p1 = Promise.resolve(11);
let p2 = Promise.resolve(22);
let promise = Promise.all([p1, p2]);
promise.then( value => {
console.log(Array.isArray(value)); // true
value.forEach(value => console.log(value));
// 输出: 11,22
});
/*
返回的 Promise 调用完成处理程序,传入一个数组参数,数组映射多个 Promise 的迭代对象的完成结果。
*/
如果传入Promise.all()的 Promise 中只要有一个被拒绝,那么返回的 Promise 也立即被拒绝,并传入相应错误信息到拒绝处理程序中。
let p1 = Promise.resolve(11);
let p2 = Promise.reject(new Error ("explosion"));
let promise = Promise.all([p1, p2]);
promise.then(null, error => {
console.log(error.message); // "explosion"
});
Promise.race()
Promise.race()也能监听多个 Promise,只接受一个参数,包含多个 Promise 的迭代对象。 当其中一个 Promise 被解决返回的 Promise 才被解决。多个 Promise 会进行竞选,当最先被解决的 Promise 是已完成状态,则返回 Promise 也是已完成状态,并且附加的完成数据传入到完成处理程序中;当最先被解决的 Promise 是已拒绝状态,则返回 Promise 也是已拒绝状态,并且附加的错误信息传入到拒绝处理程序中。
let resolve = Promise.resolve(111);
let reject = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error ("explosion")), 0);
});
let promise = Promise.race([resolve, reject]);
promise.then( value => console.log(value),
error => console.log(error.message)
); // 111
/*
resolve 是通过 Promise.resolve() 方法创建,直接创建已完成的 Promise,
所以 promise 的状态与 resolve 相同。
*/
Promise 派生类
Promise 可以作为基类派生其他类,这样就可以自定义的 Promise 的变量、方法来扩张内建 Promise 的功能。
class MyPromise extends Promise {
success (resolve, reject) {
return this.then(resolve, reject);
}
failure (reject) {
return this.catch(reject);
}
}
let resolve = new MyPromise(resolve => resolve(11));
let reject = MyPromise.reject(new Error("explosion"));
resolve.success(value => console.log(value)); // 11
reject.failure(error => console.log(error.message)); // "explosion"
扩展 async、await
async 和 await 使执行异步任务的程序语法更加简单。在函数前添加 async 关键字表示该函数以异步方式进行。await 操作符用于等待一个 Promise 对象,当 Promise 被解决,则返回该 Promise 相关附加信息。
function fun (value, flag) {
return new Promise( (resolve, reject) => {
setTimeout(() => {
if(flag) {
resolve(value);
} else {
reject(new Error("Explosion!"));
}
}, 1000);
})
}
async function asyncFun (value, flag) {
const result = await fun(value, flag);
console.log(result); // "已完成"
}
let resolve = asyncFun("已完成", true);
console.log(resolve instanceof Promise); // true
如果 await 等待的 Promise 为已完成状态,那么当前代码继续执行并且返回 Promise 的完成值。如果 Promise 为已拒绝状态,那么会抛出错误,当前代码就无法执行,asyncFun() 返回一个隐式已拒绝的 Promise。
async、await 好处就是可以按照同步方式编写异步代码。