Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。在处理promise问题的时候又绕不开async函数,在这之前又要知道事件循环(event loop),也就是代码执行的顺序。
(一)事件循环
1、宏任务和微任务
宏任务包括:
script、setTimeout、setInterval、setImmediate、I/O、UI rendering
微任务包括:MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick(会插队)、async函数中await后面的代码(下一行)。
2、事件循环规则
- 一开始整个脚本作为一个宏任务执行;
- 同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列;
- 宏任务执行结束,检查微任务队列是否具有任务,有则依次执行,直到全部执行完;
- 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空。
注意:同步代码直接放入执行栈,计时器是时间到了再放进异步队列。
执行顺序:执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
记忆:1.同步代码->2.procsee.nextTick->3.微任务->4.宏任务->5.setImmediate
(二)Promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
特点:
1.Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),Promise对象状态的改变由自身异步操作的结果决定。
2.Promise对象的状态只能改变1次,且只有两种可能:从pending变为fulfilled和从pending变为rejected。
1、基本使用:Promise对象是一个构造函数。
const promise = new Promise((resolve, reject) => {
// 普通代码
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”,并将异步操作报出的错误,作为参数传递出去。
2、then()
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。
3、catch()
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
4、finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
5、all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。全成功才成功,并返回一个数组;有一个失败返回最先失败的promise。
6、race()
Promise.race()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。返回最先结束的promise。
7、allSettled()
Promise.allSettled()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。返回所有内容。
8、any()
Promise.any()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。返回d第一个成功的。
9、resolve()和reject()
返回新的Promise实例,并更改状态。
** 总结 **
Promise的状态一经改变就不能再改变。.then和.catch都会返回一个新的Promise。catch不管被连接到哪里,都能捕获上层未捕捉过的错误。- 在
Promise中,返回任意一个非promise的值都会被包裹成promise对象。 Promise的.then或者.catch可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。.then或者.catch中return一个error对象并不会抛出错误,所以不会被后续的.catch捕获。.then或.catch返回的值不能是 promise 本身,否则会造成死循环。.then或者.catch的参数期望是函数,传入非函数则会发生值透传。.then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。.finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。
(三)async和await
async函数异步代码同步化
async函数返回一个Promise对象,可以使用then方法添加回调函数。- 当
async函数内部抛出错误的时候,会导致返回的Promise对象变为reject状态。抛出的错误对象会被.then()方法的第二个回调函数接收或者.catch()方法回调函数接收到。 await命令后面是一个Promise对象,返回该对象的结果。如果不是Promise对象,就直接返回对应的值。await的使用,必须要有async。- 写在
await后面的代码放到async创建的那个Promise里面执行;写在await下面的代码放到前一个创建的那个Promise对象的.then里面执行。
async function fun(){
return 1
}
console.log(fun());// Promise { 1 }
function fun2() {
return new Promise((reslove)=>{
reslove(1)
})
}
console.log(fun2()); // Promise { 1 }
function fun3() {
return Promise.resolve(1)
}
console.log(fun3()); // Promise { 1 }
所以async函数是promise的语法糖