异步轮询

异步

先来看一个例子

console.log('start')
setTimeout(() => {
    console.log('setTimeout')
}, 4000)
console.log('end')

这段代码的执行顺序是什么,你可能想到的结果是

start

setTimeout

end

但是,js是单线程的,不会因为一个任务而被阻塞,异步操作将会被放到队列中,等主线程执行完第一轮任务,然后再执行队列中的任务

这里稍微扯一下栈和队列

栈(stack)

image-20200723134020606

栈是一种先进后出的数据结构,可以理解为一个桶,先放进的东西在最低下,后放的东西在最外面,外面的取完了之后才能拿出最先放进去的东西;

队列(queue)

image-20200723134037360

队列是一种先进先出的数据结构,可以看做一个管道,先进的先出,后进的后出

回到正题,上面的例子中setTimeout是一个异步操作,被放到了队列中,当主线程执行完成之后再来执行队列中的任务,所以答案应该是

image-20200723134050845

在js的队列中有两种划分,一种是宏任务macrotask queue),一种是微任务microtask queue),每一个事件循环(Event Loop)都有一个微任务,会有一个或多个宏任务,每一次事件循环都会首先执行微任务执行完后从宏任务取出一个加入到微任务然后执行,直至队列中所有的任务执行结束

宏任务

  • script(整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI交互事件
  • postMessage
  • MessageChannel
  • setImmediate(Node.js 环境)

微任务

  • Promise.then
  • Object.observe
  • MutaionObserver
  • process.nextTick(Node.js 环境)

Promise

异步原理看完了,下面开始Promise

  let promise = new Promise((resolve, reject) => {
    // resolve("成功")
    // reject("失败")
  })
  console.log(promise)

先来看一下Promise对象的结构

image-20200723134434556

当没有任何操作的时候,Promise对象的状态时pending,值是undefinded

当在Promise中调用resolve之后,状态变为fulfilled,意思是成功

image-20200723134448544

当调用reject的时候,状态变为rejected,意思是失败

image-20200723134457497

Promise的状态是不可逆的,resolve和reject同时出现,先执行的决定状态,后执行的不生效

下面这三种方法可以触发Promise.then的错误方法

  • throw new Error(‘fail’)
  • reject(‘fail’)
  • 错误操作(默认,类似try/catch)

链式调用

console.log('start')
let promise = new Promise((resolve, reject) => {
    console.log('promise')
    resolve("success")
    // reject("faild")
}).then(value => {
    console.log(value+1)
    return value
}, reason => {
    console.log(reason)
}).then(value => {
    console.log(value+2)
}, reason => {
    console.log(reason)
})
console.log('end')

打印结果为

image-20200723134559383

Promise会将返回值作为.then()的参数传递下去,而且两个.then是在主线程执行完成之后才执行的,这里被放到了微任务(不是并列的,当执行第一个.then的时候第二个.then才会被放到微任务),如果不调用resolve或者reject,微任务是不创建的

每一个Promise.then也是一个Promise

状态中转

let promiseErr = new Promise((resolve, reject) => {
    reject('faild')
})

let promise = new Promise((resolve, reject) => {
    resolve(promiseErr)
}).then(value => {
    console.log('value:'+value)
}, reason => {
    console.log('err:'+reason);
})

这里promise虽然调用了resolve,但是返回的状态却是promiseErr的,所以他的结果是

image-20200723134642995

.then是对它前一个Promise对象的操作

错误捕获

这样每一个then都有两个方法,过于繁琐,可以在then中只写成功方法,在最后使用catch方法捕获错误

let promise = new Promise((resolve, reject) => {
    resolve()
}).then(v => {
    console.log('p1')
    throw new Error('fail')
}).then(v => {
    console.log('p2')
}).catch(err => {
    console.log(err)
}).then(v => {
    console.log('p3')
}).then(v => {
    console.log('p4')
})

结果为

image-20200723134735926

有结果可以看出,发生错误的地方到catch之间的代码不会执行,catch返回值也是一个Promise对象

async/await

了解了Promise之后,async/await就很容易了,它其实就是Promise的语法糖

async function asyncFunc() {
    return "asunc"
}

let result = asyncFunc()
console.log(result);

image-20200723134849722

从结果看出,async方法返回的也是一个Promise对象

来看看下面这个例子

function p() {
    console.log('p')
}
async function func() {
    await p();
    console.log('OK')
}
console.log('start')
func()
console.log('end')
// start
// p
// end
// OK

用知乎上看到过一个例子来解释,可以将

async function func() {
    await p();
    console.log('OK')
}

理解为

function func() {
    return Promise.resolve(p()).then(() => {
        console.log('OK')
    })
}

console.log(‘OK’)被分配到微任务中等待下一轮轮询

来看一个综合案例

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')

输出结果是

script start

async1 start

async2

promise1

script end

async1 end

promise2

settimeout

你做对了么(๑´ㅂ`๑)


前端小白