1. 宏任务与微任务
1.1 什么是宏任务与微任务
我们都知道 Js 是单线程的,但是一些高耗时操作就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。
在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。
1.2 宏任务与微任务的几种创建方式
1.3 如何理解script(整体代码块)是个宏任务
实际上如果同时存在两个 script 代码块,会首先在执行第一个 script 代码块中的同步代码,如果这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,会首先去清空微任务队列,再去开启第二个 script 代码块的执行。所以这里应该就可以理解 script(整体代码块)为什么会是宏任务。
1.4 什么是EventLoop
先来看个图
-
判断宏任务队列是否为空
- 不空 --> 执行最早进入队列的任务 --> 执行下一步
- 空 --> 执行下一步
-
判断微任务队列是否为空
-
不空 --> 执行最早进入队列的任务 --> 继续检查微任务队列空不空
-
空 --> 执行下一步
-
因为首次执行宏队列中会有 script(整体代码块)任务,所以实际上就是 Js 解析完成后,在异步任务中,会先执行完所有的微任务,这里也是很多面试题喜欢考察的。需要注意的是,新创建的微任务会立即进入微任务队列排队执行,不需要等待下一次轮回。
2.Promise概述
2.1 Promise的状态
-
实例对象中的一个属性 『PromiseState』
-
pending 未决定的
-
resolved / fullfilled 成功
-
rejected 失败
-
-
promise的状态改变
-
pending变为resolved/fullfilled
-
pending变为rejected
-
- 只有这两种变化可能,也就是说不可能由resolved变为rejected,且一个promise对象只能改变一次
- 无论变为成功还是失败,都会有一个结果数据
- 成功的结果数据一般称为value,失败的结果数据一般称为reason
2.2 Promise 对象的值
实例对象中的另一个属性 『PromiseResult』 保存着异步任务『成功/失败』的结果,下面两个函数可以修改对返回的结果进行修改,其他皆不可以
- resolve()
- reject ()
2.3Promise 的基本流程
3. Promise的使用
3.1 API
- Promise构造函数:Promise(executor){}
- executor函数:执行器(resolve,reject) => {}
- resolve函数:内部定义成功时我们调用的函数 value => {}
- reject函数:内部定义失败时我们调用的函数 reason => {}
- executor函数:执行器(resolve,reject) => {}
- 说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行
<script>
let p = new Promise((resolve,reject)=>{
//同步调用
console.log(111);
});
console.log(222);
</script>
-
Promise.prototype.then方法:(onResolved,onReject) => {}
-
onResolved函数:成功的回调函数 (value) => {}
-
onReject函数:失败的回调函数 (reason) => {}
-
- 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调返回一个新的promise对象
- Promise.prototype.catch方法:(onRejected) => {}
- onRejected函数:失败的回调函数 (reason) =>{}
- 说明:then()的语法糖,相当于:then(undefined,onRejected)
<script>
let p = new Promise((resolve,reject)=>{
//修改promise对象的状态
reject('error')
});
//执行catch方法
p.catch(reason =>{
console.log(reason); //输出error
})
</script>
- Promise.resolve方法:(value) => {}
- value:成功的数据或promise对象
- 说明:返回一个成功/失败的 promise 对象
<script>
let p1 = Promise.resolve(123);
//如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
console.log('p1:',p1);
//如果传入的参数为Promise对象,则参数的结果决定resolve的结果
let p2 = Promise.resolve(new Promise((resolve,reject) => {
//成功的结果
resolve('OK')
}))
console.log("p2:",p2);
let p3 = Promise.resolve(new Promise((resolve,reject) => {
//失败的结果
reject('error');
}))
console.log("p3:",p3);
</script>
- Promise.reject 方法: (reason) => {}
- reason:失败的原因或promise对象
说明: 返回一个失败的 promise ==对象==
<script>
let p1 = Promise.reject(456);
//如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
console.log('p1:',p1);
//如果传入的参数为Promise对象,则参数的结果决定resolve的结果
let p2 = Promise.reject(new Promise((resolve,reject) => {
resolve('OK')
}))
//即使传入的时一个成功的promise对象,返回的也是失败的promise对象
console.log("p2:",p2);//结果PromiseResult是传入的参数OK
let p3 = Promise.reject(new Promise((resolve,reject) => {
//失败的结果
reject('error');
}))
console.log("p3:",p3);
</script>
- Promise.all方法:(promises) =>{}
- promises:包含n个promise的数值
- 说明:返回一个新的promise,只有所有的promise都成功了才成功,只要有一个失败了就直接失败
<script>
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
let p4 = Promise.reject('Error');//失败的
//成功的情况
const resultSuccess = Promise.all([p1, p2, p3]);
console.log("resultSuccess:",resultSuccess);
//失败的情况
const resultError = Promise.all([p1, p2, p4]);
console.log("resultError:",resultError);
</script>
- Promise.race方法:(promises) =>{}
- promises:包含n个promise的数组
- 说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
<script>
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
let p4 = new Promise((resolve, reject) => { //添加定时器,异步任务
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p5 = Promise.reject('Error'); //失败的结果
//调用
const result1 = Promise.race([p1, p2, p3]);
console.log("result1",result1);
const result2 = Promise.race([p4, p2, p3]);
console.log("result2",result2);
const result3 = Promise.race([p4, p5,p1]);
console.log("result3",result3);
const result4 = Promise.race([p5, p1, p2]);
console.log("result4",result4);
</script>
3.2 Promise 的几个关键问题
-
如何改变promise的状态
-
resolve(value):如果当前是pending就会变为resolved
-
reject(reason): 如果当前是 pending 就会变为 rejected
-
抛出异常: 如果当前是 pending 就会变为 rejected
-
-
一个 promise 指定多个成功/失败回调函数, 都会调用吗?
- 当 promise 改变为对应状态时都会调用
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');//改变状态
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
</script>
- 改变 promise 状态和指定回调函数谁先谁后?
(1)都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再==指定==回调
<script>
//先改变状态,再指定回调
let p1 = new Promise((resolve, reject) => {
resolve('OK');//同步任务
});
p1.then(value => {
console.log(value);
},reason=>{
})
//先指定回调,再改变状态
let p2 = new Promise((resolve, reject) => {
//异步任务
setTimeout(() => {
resolve('OK');
},3000)
});
//then先执行,而不是then里面的回调函数先执行,所以value会再3s后输出
p2.then(value => {
console.log(value);//状态改变才能拿到数据
},reason=>{
})
</script>
(2)如何先改状态再指定回调?
- ① 在执行器中直接调用 resolve()/reject()
- ② 延迟更长时间才调用 then()
(3) 什么时候才能得到数据?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
- promise.then()返回的新 promise 的结果状态由什么决定?
(1)简单表达: 由 then()指定的回调函数执行的结果决定
(2)详细表达:
- 如果抛出异常,新promise变为rejected,reason为抛出的异常
- 如果返回的是非promise的任意值,新promise变为resolved,value为返回值
- 如果返回的是另一个新的promise,此promise的结果就会成为新promise的结果
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
// console.log(value);
//1. 抛出错误
// throw '出了问题';
//2. 返回结果是非 Promise 类型的对象
// return 123;
//3. 返回结果是 Promise 对象
return new Promise((resolve, reject) => {
resolve('success');
// reject('error');
});
}, reason => {
console.warn(reason);
});
console.log(result);
</script>
-
promise 如何串连多个操作任务?
-
promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用
-
通过 then 的链式调用串连多个同步/异步任务
-
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value); //success
}).then(value => {
console.log(value);
//undefined,因为第二个then没有return,所以return undefined,所以
//第二个then的返回结果是一个成功的promise且成功的结果是undefined,而不是返回一个promise对象
//所以第三个then会输出第二个then返回的结果
})
</script>
- promise 异常传透?
- 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,前面任何操作出了异常, 都会传到最后失败的回调中处理
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 1000);
});
p.then(value => {
// console.log(111);
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => { //catch方法最后对错误进行统一的处理
console.warn(reason);
});
</script>
-
中断 promise 链
-
当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
-
办法: 在回调函数中返回一个 pendding 状态的 promise 对象(有且只有一个方式)
-
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一个方式
return new Promise(() => {}); //返回一个pending状态的promise对象,状态不改变,后面的回调不再执行
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
4. async与await
4.1 async函数
- 函数的返回值为 promise 对象
- promise 对象的结果由 async 函数执行的返回值决定
<script>
//then
async function main(){
//1. 如果返回值是一个非Promise类型的数据
// return 521;
//2. 如果返回的是一个Promise对象
// return new Promise((resolve, reject) => {
// // resolve('OK');
// reject('Error');
// });
//3. 抛出异常
throw "Oh NO";
}
let result = main();
console.log(result);
</script>
4.2 awai表达式
- await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
- 如果表达式是 promise 对象, await 返回的是 promise 成功的值
- 如果表达式是其它值, 直接将此值作为 await 的返回值
<script>
async function main(){
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1. 右侧为promise的情况,await 返回的是 promise 成功的值
// let res = await p;
// console.log(res);
//2. 右侧为其他类型的数据,直接将此值作为 await 的返回值
let res2 = await 20;
console.log(res2);
//3. 如果promise是失败的状态,用try catch进行捕获
try{
let res3 = await p;
}catch(e){
console.log(e);
}
}
main();
</script>
4.3 注意
- await 必须写在 async 函数中, 但 async 函数中可以没有 await
- 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
4.4 async与await结合使用
/**
* resource 1.html 2.html 3.html 文件内容
*/
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);
// //回调函数的方式
// fs.readFile('./resource/1.html', (err, data1) => {
// if(err) throw err;
// fs.readFile('./resource/2.html', (err, data2) => {
// if(err) throw err;
// fs.readFile('./resource/3.html', (err, data3) => {
// if(err) throw err;
// console.log(data1 + data2 + data3);
// });
// });
// });
//async 与 await
async function main(){
try{
//读取第一个文件的内容
let data1 = await mineReadFile('./resource/1.html');
let data2 = await mineReadFile('./resource/2.html');
let data3 = await mineReadFile('./resource/3.html');
console.log(data1 + data2 + data3);
}catch(e){
console.log(e.code);
}
}
main();