文章目录
一、Promise
1.1 promise原理
promise 可以看作是一个对象,是异步编程的一种解决方案,可以获取异步操作的消息。
- promise实际上是一个构造函数,当被创建时便会立即执行里面的内容
new Promise((resolve, reject) => {
console.log('Promise')
resolve(1)
})
//结果 new Promise
- promise有三个状态,分别是pending(进行中)、resolved(已成功)和 rejected(已失败),一旦其处于resolved和 rejected状态,便不可再改变。promise的原型有一个then()方法,其接收两个函数作为参数,第一个参数是 Promise 执行成功resolved()时的回调,第二个参数是 Promise 执行失败时rejected()的回调,两个函数只会有一个被调用。成功的回调必选,失败的回调可选。
new Promise((resolve, reject) => {
console.log('Promise')
resolve(1)
}).then(function(value){
console.log(value); //此处then调用了resolve(1),输出结果为:Promise 1
return Promise.reject('reject'); //返回一个promise
}).then(function(value){ // 只能调用其中一个函数,此处调用了reject,输出reject:reject
console.log('resolve:' + value);
}, function(err) {
console.log('reject:' + err);
});
- 通过多次调用.then(),可以添加多个回调函数,它们会按照插入顺序并且独立运行,即Promise 的链式编程。若在 then 中使用了 return,那么 return 的值会被 Promise.resolve()包装,然后被下一个then调用。
const p = new Promise(function(resolve,reject){
resolve(1);
}).then(function(value){ // 第一个then输出:1
console.log(value);
return value * 2;
}).then(function(value){ // 第二个then输出:2
console.log(value);
})
总体过程:启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
1.2 promise优缺点
优点:promise的链式结构可以解决回调地狱问题
缺点:
- 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
二、Async await
2.1 async await原理
async可以看成promise的进阶,放在函数前面表明这是一个异步函数,会返回一个Promise对象,await必须放在async函数里面,若是放在外面会得到一个语法错误。async 函数执行时,如果遇到 await 就会先暂停执行 ,等到await后面触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
- 普通async await执行过程:
let s = 0
let f = async () => {
s = s + await 10
console.log('4', s) // -> 4 10
}
f()
s++
console.log('1', s) // -> 1 1
//输出顺序:1 1 4 10
- 定义s全局变量
- 定义了一个f的async异步函数
- 调用f:在f函数中,遇到await,s等待返回,此时在f函数内s=10,这时候async函数内部且在await下面的代码暂停执行,先执行s++,在外部,s=0,s++则为1
- console.log('1', s) ->输出 1 1,再回过头去执行await下面的console.log('1', s)
- 最终输出结果便为:1 1 4 10
- 此处涉及到js的单线程运行机制,时间循环机制,同步任务和异步任务,宏任务和微任务,详解见下文。
- 前面说到async函数返回的是一个promise对象,async 就是将函数返回值使用 Promise.resolve() 包裹起来,当使用then调用时,就相当于Promise在用then调用resolved。
let s = 0
let f = async () => {
s = s + await 10
console.log('4', s) // -> 4 10
}
f().then(console.log('3', s))
s++
console.log('1', s) // -> 1 1
//输出顺序:3 0 1 1 4 10
- 定义s全局变量
- 定义了一个f的async异步函数
- 调用f:在f函数中,遇到await,s等待返回,此时在f函数内s=10,这时候async函数内部且在await下面的代码暂停执行
- 同时使用then方法添加回调函数或者执行相关操作(此处是直接console出值),因此先执行then函数,输出3 0
- 接着往下s++,并执行console.log('1', s),最后回到await下面的代码,执行console.log('4', s)
使用then方法添加回调函数的情况:
async function helloAsync(){
return "Async";
}
console.log(helloAsync()) // Promise {<resolved>: "Async"}
helloAsync().then(val=>{
console.log(val); // Async
})
- await返回值返回的是Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
function testAwait(){
console.log("testAwait");
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
- 如果等待的是Promise对象,则resolve,然后再恢复 async 函数的执行并返回解析值
function testAwait (x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function helloAsync() {
var x = await testAwait ("hello world");
console.log(x);
}
helloAsync ();
// hello world
2.2 async较Promise的优点
- 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但存在 then 链式调⽤的阅读负担)
- 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
- 调试时的阅读性, 也相对更友好
三、Promise 和 Async await 异步执行原理案例分析
3.1 原理分析
在js的运行机制中,程序是单线程运行的,就是说一次只能执行一个任务,其他任务只能等待被执行。任务分为同步任务和异步任务,异步任务又分为宏任务和微任务。可以这样来理解,同步任务放在执行栈(调用堆)中,异步任务放在任务队列中。主线程每次运行时,所有同步任务都按照顺序排列在执行栈(调用堆)中依次等待主线程调用和执行,同步任务都执行完毕之后,再去任务队列中调用微任务和宏任务,执行顺序便是,每清空一次所有的微任务,再执行一次宏任务,以此循环,这个过程又叫js的事件循环机制。 宏任务:所有 js 代码,setTimeOut、setInverter、setImmediate 、 MessageChannel,I/O,UI Rendering等。 微任务:promise 的 then,catch,finaly,process.nextTick,MutationObserver(监测 dom 的变更)等。
3.2 案例分析
console.log("script start");
Promise.resolve().then(function(){
console.log("promise1")
})
setTimeout(function(){
console.log("setTimeout")
},0);
Promise.resolve().then(function(){
console.log("promise2")
})
console.log("script end");
//输出
//script start
//script end
//promise1
//promise2
//setTimeout
| 微任务 | 宏任务 |
|---|---|
| promise1 | setTimeout |
| promise2 | |
| 解析: |
- 从上往下执行,script start和script end是同步任务,先输出(满足同步任务先执行的条件)
- promise1是微任务,先放到微任务队列
- setTimeout是宏任务,放到宏任务队列
- promise2是微任务,继续放到微任务队列
- 先清空微任务,因此按顺序输出promise1和promise2
- 再来执行宏任务setTimeout
- 输出 script start script end promise1 promise2 setTimeout
console.log('script start');
Promise.resolve().then(function() {
setTimeout(function() {
console.log('setTimeout1');
}, 0);
}).then(function() {
console.log('promise1');
});
setTimeout(function() {
console.log('setTimeout2')
Promise.resolve().then(function(){
console.log('promise2');
})
},0)
console.log('script end');
//输出:
//script start
//script end
//promise1
//setTimeout2
//promise2
//setTimeout1
| 微任务 | 宏任务 |
|---|---|
| promise1 | setTimeout2 |
| promise2 | setTimeout1 |
- 先清空第一个微任务1 promise1,再执行一个与微任务1 并列的宏任务1 setTimeout2
- 再继续查找微任务,在宏任务内嵌套了一个微任务2 promise2,先清空,再执行另一个与微任务2 并列的宏任务2 setTimeout1
- 输出: script start script end promise1 setTimeout2 promise2 setTimeout1
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log("setTimeout")
})
new Promise(resolve => {
console.log('promise')
resolve()
}).then(function () {
console.log("promise1")
})
.then(function () {
console.log("promise2")
})
console.log('script end')
分为主线程的执行栈和任务队列 主线程:先执行完同步任务 任务队列:先清空微任务,再执行宏任务
整个程序的输出结果为: script start async2 end promise script end async1 end promise1 promise2 setTimeout 这里可能有同学会疑惑这里的script start和script end为什么不是跟前面案例一样一起输出。其实都是按顺序执行下来的,这个也是,只是前面的案例中途没有输出其他的,反应比较快。
小tip:微任务队列清空后执行宏任务,执行宏任务队列时不会等队列清空再去执行微任务,执行一个宏任务之后就会去检查清空微任务队列,再回来执行宏任务队列。