什么是异步
异步就是把任务分成上下两段,先执行任务上半段,在转而执行其他同步任务。等到其他同步任务全部执行完毕后 || 异步状态发生了改变,再继续执行任务下半段。
回调函数
在promise出来之前,javascript处理异步一般通过回调函数,但是存在一个问题,回调函数一多的话,代码的耦合性就高了,一步错步步错,不容易维护,俗称:回调地狱。
promise是es6的一种异步解决方案
promise解决了回调地狱的问题,Promise是一个对象,它的内部保存了未来才会结束的事件的结果。
promise对象有三种状态:pending、resolved、rejected,一旦状态改变就不会在改变了。
Promise接受一个函数,这个函数又有两个参数,分别为resolve、reject函数,它们是由Javascript引擎提供的。
promise对象有then方法,接受两个函数参数:
- 当promise对象的状态变为resolved的时候,把resolve()传递的值传给then的第一个函数执行
- 当promise对象状态变为rejected时候,把reject()传递的值传给then的第二个函数执行
- then方法返回一个新的promise对象,和原来的promise不是一个对象。所以,then方法,支持链式操作。
catch方法
我们一般不写then的第二个函数参数,使用promise的catch方法。
let promise = new Promise(function(resolve,reject){
setTimeout(function(){
try{
console.log(123)
resolve('the first')
}catch(e){
reject(e)
}
},1000)
})
console.log(promise)
//promise.then(
//msg=>{
// console.log(msg)
//},
//err=>{
// console.log(err)
//}
//)
//catch
promise.then(msg=>{
console.log(msg)
}
).catch(err=>{
console.log(err)
})
console.log(456)
Promise.all()、Promise.race()
Promise.resolve()、Promise.reject()
const p = Promise.resolve('成功了')
p.then(msg=>console.log(msg))
//相当于下面的写法
const p2 = new Promise(function(resolve,reject){
resolve('成功了')
})
p2.then(msg=>console.log(msg))
const p3 = new Promise(function(resolve,reject){
reject('失败了')
})
p3.then(msg=>console.log(msg))
.catch(err=>console.log(err))
const p4 = Promise.reject('失败了')
p4.then(msg=>console.log(msg))
.catch(err=>console.log(err))
async函数
async函数是Generator生成器函数的语法糖,它相较generator函数来说有哪些改进之处:
- 内置了执行器,不需要像generator函数一样,要通过next()才能往下执行
- async的语义更清晰,async代表了函数中有异步操作,await表示后面的表达式需要等待执行的结果。
- async函数返回的是promise对象,而generator函数返回的是iterator对象。你可以很方便的调用then()函数处理异步。
为什么出现async函数呢?
虽然promise解决了回调地狱的问题,但是通过then处理异步回调,写起来还是很麻烦。async函数相比家promise来说,简洁不少。
不过,async也有它的缺点:
await会把异步变成同步,如果两个异步操作没有关联性(继发性),可以使用Promise.all([p1,p2]),让它们并行。这样,性能会快点。
async继发、并发
//继发
console.time('start')
async function fn() {
let res = await new Promise(res=>{res(123)})
let res2 = await Promise.resolve(res+'456')
console.log(res)
console.log(res2)
console.timeEnd('start')
}
console.log(fn())
console.log(789)
//并发
console.time('start')
async function fn() {
let p1 = Promise.resolve(123)
let p2 = Promise.resolve(456)
let res = await p1
let res2 = await p2
console.log(res)
console.log(res2)
console.timeEnd('start')
}
console.log(fn())
console.log(789)
//并发2
console.time('start')
async function fn() {
let p1 = Promise.resolve(123)
let p2 = Promise.resolve(456)
let res3 = await Promise.all([p1,p2])
console.log(res3)
console.timeEnd('start')
}
console.log(fn())
console.log(789)
异步执行顺序、Event Loop
setTimeout(function (x) {
console.log(x)
},0,789)
console.log(456)
//456--->789
//分析:虽然setTimeout的第二个参数是0,但是不会立即执行里面的函数。
//这里setTimeout的工作过程:0代表了立即把函数放到task队列,但是不会立即
//执行。需要等到其他同步任务执行完毕,Event Loop会把task队列中的函数拿到
//执行栈中执行。
let promise = new Promise(function (resolve,reject) {
resolve(123)
console.log(789)
})
console.log(promise)
promise.then(msg=>console.log(msg))
console.log(456)
// 789--->Promise {<resolved>: 123}--->456--->123
//分析:先立即执行Promise中的函数,resolve(123)会让promise对象状态变为
//成功,打印789,执行完异步任务上半段,打印promise对象,状态为成功,值为
//123。这个时候,不会去执行then中的回调。因为,异步任务的执行是先执行完上
//半段,再转而执行其他同步任务,等到其他同步任务都执行完毕时,如果异步任务
//上半段执行返回的状态改变了,再去执行下半段回调函数。
let promise = new Promise(function (resolve,reject) {
setTimeout(resolve,2000,123)
console.log(789)
})
console.log(promise)
promise.then(msg=>console.log(msg))
console.log(456)
// 789--->Promise {<pending>}--->456--->123
//分析:2秒过后会把resolve函数放到task队列中,打印789,打印promise,
//打印456.同步代码执行完毕,从task队列拿出resolve函数放到执行栈调用,
//状态改变了,执行then
async function fn() {
console.log(789)
let res = await 123
console.log(res)
}
console.log(fn())
console.log(456)
//789--->Promise {<pending>}--->456--->123
//分析:打印789,await代表异步操作,后面的表达式不是promise对象,通过
//Promise.resolve(123)把它转换为promise对象,执行完这个异步任务的
//上半段。天跳出async函数执行别的同步代码。打印async的返回值是个pending
//状态的promise对象,打印456.同步代码执行完毕,回到await处,
//Promise.resolve(123)状态已经改变,res的值就是promise对象的值,
//打印res
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
//首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await
//内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0
//被保存了下来
//因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成
//Promise.reslove(返回值),然后会去执行函数外的同步代码
//同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候
//a = 0 + 10
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')
}, 0)
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
// => promise1 => promise2 => async1 end => setTimeout
首先先来解释下上述代码的 async 和 await 的执行顺序。当我们调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await的时候会就让出线程开始执行 async1 外的代码,所以我们完全可以把 await 看成是让出线程的标志。
然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,接下来去执行 then 中的回调,当两个 then 中的回调全部执行完毕以后,又会回到 await 的位置处理返回值,这时候你可以看成是 Promise.resolve(返回值).then(),然后 await 后的代码全部被包裹进了 then 的回调中,所以 console.log(‘async1 end’) 会优先执行于 setTimeout。