乐趣区

关于javascript:大前端进阶js异步编程

学不动了怎么办?换个姿态持续学。

JS 引擎

js 引擎执行过程分为三个阶段:

  • 解释阶段

js 代码加载结束后进入此阶段,此阶段实现由字符串到机器码的转变过程。

  • 预处理(编译)阶段与执行阶段

因为 js 是解释性语言,所以会一边编译一边执行。创立变量对象产生在预编译阶段(编译阶段生成执行上下文,执行上下文蕴含变量,作用域链和 this),变量赋值产生在执行阶段。

同步 VS 异步

浏览器内核是多线程的,和 js 执行相干的线程蕴含:

  1. js 引擎主线程,负责解释和执行 js 代码。
  2. ajax 申请线程。
  3. DOM 解决线程。
  4. 定时工作线程。

所有同步工作都是在 js 引擎主线程上依照程序一次执行,造成一个执行栈,工作的执行也是工作在执行栈上的一直入栈和出栈。除了执行栈,事件触发线程治理一个工作队列,异步工作有了后果,会向工作队列放入回调。一旦所有同步工作执行结束之后(js 引擎闲暇),会读取工作队列,将工作放入执行栈并执行。

** 事件循环 **(Event Loop)指的是脚本运行时,先顺次运行执行栈,而后从工作队列中取出事件执行来运行工作队列中的工作,这个过程是一直反复的。

异步编程

回调函数

回调函数是 js 异步编程的罕用形式,然而回调函数容易层层嵌套造成回调天堂。

const getData = (data, callback) => {setTimeout(() => {callback(data + 1)
    }, 1000)
}

getData(1, function (v1) {getData(v1, function (v2) {getData(v2)
    })
})

层层嵌套的代码,不仅影响代码逻辑,而且可读性十分差,能够用 Promise 对象、Generator 函数、async 函数代替,使得回调扁平化。

Promise

Promise 对象提供 then 办法,then 办法能够链式调用,链式调用时 then 办法顺次执行。

const getData = function (data) {
    return new Promise(resolve => {setTimeout(() => {resolve(data + 1)
        }, 1000)
    })
}

getData(1)
    .then(v1 => {return getData(v1)
    })
    .then(v2 => {return getData(v2)
    })

Promise 对象不仅能解决回调天堂问题,同时还提供了动态的 all 和 race 办法。all 办法接管一个数组,当数组中存在异步调用的时候,只有异步办法全副执行实现之后,then 办法才执行。race 办法同样承受一个数组,与 all 不同的是,参数数组中的多个异步调用,如果其中一个实现或者失败,那么 then 办法就会执行,能够用 race 办法实现 ajax 超时判断。

const getData = function (data) {
    return new Promise(resolve => {setTimeout(() => {resolve(data + 1)
        }, 1000)
    })
}

function timeout(duration) {
    return Promise.race([getData(1),
        new Promise((resolve, reject) => {setTimeout(() => {reject(new Error('调用超时'))
            }, duration)
        })
    ])
}

timeout(500).then(value => {console.log(value)
}).catch(err => {console.log(err.message)
})

因为设置的超时工夫是 500 毫秒,此时 getData 异步函数尚未执行实现,因而 race 办法会捕捉到超时谬误。

Generator

Generator 生成器,能够利用 * 和 yield 关键字解决异步嵌套的回调天堂问题。

const getData = function (data) {
    return new Promise(resolve => {setTimeout(() => {resolve(data + 1)
        }, 1000)
    })
}

function* gen() {let v1 = yield getData(1)
    console.log(v1)
    // yield 会使函数暂停执行,当调用 next 办法时会继续执行,同时 next 办法传入的参数会作为 yield 的返回值
    let v2 = yield getData(v1)
    console.log(v2)
}

function co(generator) {let g = generator()

    function handleResult(result) {if (result.done) return
        else {result.value.then((value) => {handleResult(g.next(value))
            }, err => {console.log(err)
            })
        }
    }

    handleResult(g.next())
}

co(gen)

async

通常 async 和 await 一起应用,是一种语法糖,能够了解为通过 async 和 await 关键字能够更不便的实现上节中的 generator。

const getData = function (data) {
    return new Promise(resolve => {setTimeout(() => {resolve(data + 1)
        }, 1000)
    })
}

async function main() {let v1 = await getData(1)
    console.log(v1)
    // yield 会使函数暂停执行,当调用 next 办法时会继续执行,同时 next 办法传入的参数会作为 yield 的返回值
    let v2 = await getData(v1)
    console.log(v2)
}

main()

微工作 vs 宏工作

如果执行上面的代码:

setTimeout(() => {console.log('timeout')
}, 0)
new Promise(resolve => {resolve('promise')
}).then(v => {console.log(v)
})

会发现,同样是异步操作,Promise 的回调函数会早于 setTimeout 的回调函数,这就波及到微工作 (Promise 等) 和宏工作 (setTimeout 等)。
宏工作和微工作均为异步工作,只是执行程序不同,通常状况下,会优先执行微工作,当微工作执行结束之后,再执行宏工作。
盗用网上的执行逻辑图:

常见的宏工作有:setTimeout(等同延迟时间下,setTimeOut 的优先级高于 setImmediate),setInterval, setImmediate, I/O, UI rendering。
常见的微工作有:Promise,process.nextTick(在 node 环境中 nextTick 优先级高于 promise)。

参考:
js 运行机制及异步编程(一)
js 运行机制及异步编程(二)

退出移动版