JavaScript 是一门运行在浏览器的脚本语言,作用是实现页面的动态交互,操作 DOM决定了它是一门单线程模式的语言,否则会出现多线程的线程同步问题。
同步模式
同步模式会先在调用栈压入一个匿名函数,然后把同步代码放入这个匿名函数里,按顺序把函数的调用或者语句压入调用栈,函数和变量的声明不会压入。当执行完毕就把函数的调用或者语句取出调用栈。
缺点是如果里面有一段比较耗时的代码,会在那里发生阻塞,界面加载可能发生卡顿。
异步模式
异步模式不会等待一个任务结束再去执行另一个任务,开启了一个任务后又立即去开启另一个任务。任务完成后的后续操作一般是执行一个回调函数。异步模式使得单线程的 JavaScript 可以同时执行多个耗时长的任务,但同时也使得代码的执行顺序相比同步模式较为混乱。
对于异步代码,比如 setTimeout,运行环境会为它开启一个计时器,放入 Web APIs 中,然后先继续往下执行。Event Loop(事件循环) 有两个作用:一是监听 Web APIs, 当计时器时间到,就把任务放入队列等待执行;二是监听队列,如果调用栈里没有任务,就从队列拿出任务放入调用栈中执行。

Promise
Promise 是 ES2015 新增的一个全局类,它定义异步任务最终执行成功或者失败。
实例化 Promise- 对象需要传入一个函数作为参数,该函数会在实例化时就执行,它有两个回调函数作为参数,resolve 和 reject ,resolve 和 reject 都会返回传入的参数,resolve 是成功的回调,reject 是失败的回调,由于 Promise 的执行结果最终只能是成功或失败, 所以这两个回调也只能在最后使用一个。如果不调用 resolve 和 reject,Promise 会处于挂起(pending)状态,由运行环境来判断是否已经执行完毕了。
then 方法中回调函数会进入到队列中排队,等待调用栈的同步代码执行完毕再执行。then 方法会返回一个全新的 Promise 对象,实现 Promise 的链式调用。后面的 then 方法就是为上一个 then 返回的 Promise 注册回调。 前面 then 方法回调函数的返回值会作为传给后面 then 方法回调的参数。
var p1 = new Promise((resolve, reject) => {console.log('p1')resolve(true)// reject('error')})p1.then(value => {console.log(value) // 如果调用 resolve 则走到这里}, err => {console.log(err) // 如果调用 reject 则走到这里})console.log('end');// 输出 p1, end, true/error
异常处理
Promise 链上,任何一个异常都会被传递下去,直到被捕获。
使用 catch 捕获异常,浏览器和 Node.js 环境下也可以使用全局监听器捕获。
ajax('/api/users.json').then(function onFulfilled (value) {console.log('onFulfilled', value)return ajax('/error-url')}) // => Promise {}.catch(function onRejected (error) {console.log('onRejected', error)})// 全局捕获 Promise 异常,类似于 window.onerrorwindow.addEventListener('unhandledrejection', event => {const { reason, promise } = eventconsole.log(reason, promise)// reason => Promise 失败原因,一般是一个错误对象// promise => 出现异常的 Promise 对象event.preventDefault()}, false)// Node.js 中使用以下方式process.on('unhandledRejection', (reason, promise) => {console.log(reason, promise)// reason => Promise 失败原因,一般是一个错误对象// promise => 出现异常的 Promise 对象})
Promise 常用静态方法
resolve 和 reject
resolve 和 reject 能快速返回一个 Promise 对象。
reject 返回一个失败的 Promise 对象。
如果给 resolve 传入一个值,那么它返回一个成功的对象,如果传入一个 Promise 对象,那么它直接返回这个对象,如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行
// 常用 Promise 静态方法function ajax (url) {return new Promise(function (resolve, reject) {// foo()// throw new Error()var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = function () {if (this.status === 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send()})}Promise.resolve('foo').then(function (value) {console.log(value) // foo})new Promise(function (resolve, reject) {resolve('foo')})// 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回var promise = ajax('/api/users.json')var promise2 = Promise.resolve(promise)console.log(promise === promise2) // true// 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行Promise.resolve({then: function (onFulfilled, onRejected) {onFulfilled('foo')}}).then(function (value) {console.log(value)})// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由Promise.reject(new Error('rejected')).catch(function (error) {console.log(error)})Promise.reject('anything').catch(function (error) {console.log(error)})
并行执行
Promise.all()
如果说同时执行多个 Promise,它们之间没有依赖,或者说需要它们的所有执行结果才能往下执行其它代码,那么可以用 Promise.all() 方法,把多个 Promise 放入一个数组里作为参数传入。Promise.all() 也是返回一个新的 Promise 对象,并且回调函数能接收到 Promise 数组执行的结果数组。
如果有一个失败的,会立即抛出错误,并且 reject 的是第一个抛出的错误信息。
Promise.race()
Promise.race() 和 Promise.all() 一样可以并行执行多个 Promise,区别是一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
执行时序
一般把回调队列的任务称为宏任务,宏任务执行过程中有可能加上一些额外的需求,这种需求将作为宏任务进入队列排队执行,或者作为微任务立即执行。Promise 的回调是微任务,不用重新到队列排队。其它异步调用是微任务的还有一个 MutaionObserver 和 Node.js 下的 process.nextTick
// 微任务console.log('global start')// setTimeout 的回调是 宏任务,进入回调队列排队setTimeout(() => {console.log('setTimeout')}, 0)// Promise 的回调是 微任务,本轮调用末尾直接执行Promise.resolve().then(() => {console.log('promise')}).then(() => {console.log('promise 2')}).then(() => {console.log('promise 3')})console.log('global end') // 输出顺序global start, global end, promise, promise 2, promise 3, setTimeout
缺点
Promise 的缺点是仍然可能出现多个回调,虽然避免了嵌套地狱,但是代码可读性仍然不能和同步代码相比较。
Generator
Generator 是 ES2015 提供的生成器函数。使用 Generator 可以实现另一套异步编程方式。
// 生成器函数回顾function * foo () {console.log('start')try {const res = yield 'foo'console.log(res)const res1 = yield 'zce'} catch (e) {console.log(e)}}const generator = foo()const result = generator.next()console.log(result)const res1 = generator.next('bar')console.log(res1)console.log(generator.next())// generator.throw(new Error('Generator error')) // 可以抛出 generator 的错误// 输出顺序:// start// { value: 'foo', done: false }// bar // 这里输出 bar 是因为 generator 的第2个 next 传入了一个值给了 foo 函数里的 res 变量,如果没有传入值这里就是输出 undefined// { value: 'zce', done: false }// { value: undefined, done: true }
使用 Generator 配合 Promise,用递归实现依次执行,直到 generator 返回的对象的 done 属性为 true,就停止递归,整个 Generator 函数执行完毕。
// Generator 配合 Promise 的异步方案function ajax (url) {return new Promise((resolve, reject) => {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response)} else {reject(new Error(xhr.statusText))}}xhr.send()})}function * main () {try {const users = yield ajax('/api/users.json')console.log(users)const posts = yield ajax('/api/posts.json')console.log(posts)const urls = yield ajax('/api/urls11.json')console.log(urls)} catch (e) {console.log(e)}}// 执行器函数function co (generator) {const g = generator()function handleResult (result) {if (result.done) return // 生成器函数结束result.value.then(data => {handleResult(g.next(data))}, error => {g.throw(error)})}handleResult(g.next())}co(main)
Async 函数
ES2017 中加入了 Async / Await 语法糖,提供了语言层面的异步编程标准。
// Async / Await 语法糖function ajax (url) {return new Promise((resolve, reject) => {var xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.responseType = 'json'xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.response)} else {reject(new Error(xhr.statusText))}}xhr.send()})}async function main () {try {const users = await ajax('/api/users.json')console.log(users)const posts = await ajax('/api/posts.json')console.log(posts)const urls = await ajax('/api/urls.json')console.log(urls)} catch (e) {console.log(e)}}const promise = main() // async 函数返回一个 Promise 对象promise.then(() => {console.log('all completed')})
