1. JavaScript 是一门运行在浏览器的脚本语言,作用是实现页面的动态交互,操作 DOM决定了它是一门单线程模式的语言,否则会出现多线程的线程同步问题。

同步模式

同步模式会先在调用栈压入一个匿名函数,然后把同步代码放入这个匿名函数里,按顺序把函数的调用或者语句压入调用栈,函数和变量的声明不会压入。当执行完毕就把函数的调用或者语句取出调用栈。
缺点是如果里面有一段比较耗时的代码,会在那里发生阻塞,界面加载可能发生卡顿。
同步模式.gif

异步模式

异步模式不会等待一个任务结束再去执行另一个任务,开启了一个任务后又立即去开启另一个任务。任务完成后的后续操作一般是执行一个回调函数。异步模式使得单线程的 JavaScript 可以同时执行多个耗时长的任务,但同时也使得代码的执行顺序相比同步模式较为混乱。
对于异步代码,比如 setTimeout,运行环境会为它开启一个计时器,放入 Web APIs 中,然后先继续往下执行。Event Loop(事件循环) 有两个作用:一是监听 Web APIs, 当计时器时间到,就把任务放入队列等待执行;二是监听队列,如果调用栈里没有任务,就从队列拿出任务放入调用栈中执行。
异步模式.gif

image.png

Promise

Promise 是 ES2015 新增的一个全局类,它定义异步任务最终执行成功或者失败。
实例化 Promise- 对象需要传入一个函数作为参数,该函数会在实例化时就执行,它有两个回调函数作为参数,resolve 和 reject ,resolve 和 reject 都会返回传入的参数,resolve 是成功的回调,reject 是失败的回调,由于 Promise 的执行结果最终只能是成功或失败, 所以这两个回调也只能在最后使用一个。如果不调用 resolve 和 reject,Promise 会处于挂起(pending)状态,由运行环境来判断是否已经执行完毕了。
then 方法中回调函数会进入到队列中排队,等待调用栈的同步代码执行完毕再执行。then 方法会返回一个全新的 Promise 对象,实现 Promise 的链式调用。后面的 then 方法就是为上一个 then 返回的 Promise 注册回调。 前面 then 方法回调函数的返回值会作为传给后面 then 方法回调的参数。

  1. var p1 = new Promise((resolve, reject) => {
  2. console.log('p1')
  3. resolve(true)
  4. // reject('error')
  5. })
  6. p1.then(value => {
  7. console.log(value) // 如果调用 resolve 则走到这里
  8. }, err => {
  9. console.log(err) // 如果调用 reject 则走到这里
  10. })
  11. console.log('end');
  12. // 输出 p1, end, true/error

异常处理

Promise 链上,任何一个异常都会被传递下去,直到被捕获。
使用 catch 捕获异常,浏览器和 Node.js 环境下也可以使用全局监听器捕获。

  1. ajax('/api/users.json')
  2. .then(function onFulfilled (value) {
  3. console.log('onFulfilled', value)
  4. return ajax('/error-url')
  5. }) // => Promise {}
  6. .catch(function onRejected (error) {
  7. console.log('onRejected', error)
  8. })
  9. // 全局捕获 Promise 异常,类似于 window.onerror
  10. window.addEventListener('unhandledrejection', event => {
  11. const { reason, promise } = event
  12. console.log(reason, promise)
  13. // reason => Promise 失败原因,一般是一个错误对象
  14. // promise => 出现异常的 Promise 对象
  15. event.preventDefault()
  16. }, false)
  17. // Node.js 中使用以下方式
  18. process.on('unhandledRejection', (reason, promise) => {
  19. console.log(reason, promise)
  20. // reason => Promise 失败原因,一般是一个错误对象
  21. // promise => 出现异常的 Promise 对象
  22. })

Promise 常用静态方法

resolve 和 reject

resolve 和 reject 能快速返回一个 Promise 对象。
reject 返回一个失败的 Promise 对象。
如果给 resolve 传入一个值,那么它返回一个成功的对象,如果传入一个 Promise 对象,那么它直接返回这个对象,如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行

  1. // 常用 Promise 静态方法
  2. function ajax (url) {
  3. return new Promise(function (resolve, reject) {
  4. // foo()
  5. // throw new Error()
  6. var xhr = new XMLHttpRequest()
  7. xhr.open('GET', url)
  8. xhr.responseType = 'json'
  9. xhr.onload = function () {
  10. if (this.status === 200) {
  11. resolve(this.response)
  12. } else {
  13. reject(new Error(this.statusText))
  14. }
  15. }
  16. xhr.send()
  17. })
  18. }
  19. Promise.resolve('foo')
  20. .then(function (value) {
  21. console.log(value) // foo
  22. })
  23. new Promise(function (resolve, reject) {
  24. resolve('foo')
  25. })
  26. // 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回
  27. var promise = ajax('/api/users.json')
  28. var promise2 = Promise.resolve(promise)
  29. console.log(promise === promise2) // true
  30. // 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,Promise.resolve 会将这个对象作为 Promise 执行
  31. Promise.resolve({
  32. then: function (onFulfilled, onRejected) {
  33. onFulfilled('foo')
  34. }
  35. })
  36. .then(function (value) {
  37. console.log(value)
  38. })
  39. // Promise.reject 传入任何值,都会作为这个 Promise 失败的理由
  40. Promise.reject(new Error('rejected'))
  41. .catch(function (error) {
  42. console.log(error)
  43. })
  44. Promise.reject('anything')
  45. .catch(function (error) {
  46. console.log(error)
  47. })

并行执行

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

  1. // 微任务
  2. console.log('global start')
  3. // setTimeout 的回调是 宏任务,进入回调队列排队
  4. setTimeout(() => {
  5. console.log('setTimeout')
  6. }, 0)
  7. // Promise 的回调是 微任务,本轮调用末尾直接执行
  8. Promise.resolve()
  9. .then(() => {
  10. console.log('promise')
  11. })
  12. .then(() => {
  13. console.log('promise 2')
  14. })
  15. .then(() => {
  16. console.log('promise 3')
  17. })
  18. console.log('global end') // 输出顺序global start, global end, promise, promise 2, promise 3, setTimeout

缺点

Promise 的缺点是仍然可能出现多个回调,虽然避免了嵌套地狱,但是代码可读性仍然不能和同步代码相比较。

Generator

Generator 是 ES2015 提供的生成器函数。使用 Generator 可以实现另一套异步编程方式。

  1. // 生成器函数回顾
  2. function * foo () {
  3. console.log('start')
  4. try {
  5. const res = yield 'foo'
  6. console.log(res)
  7. const res1 = yield 'zce'
  8. } catch (e) {
  9. console.log(e)
  10. }
  11. }
  12. const generator = foo()
  13. const result = generator.next()
  14. console.log(result)
  15. const res1 = generator.next('bar')
  16. console.log(res1)
  17. console.log(generator.next())
  18. // generator.throw(new Error('Generator error')) // 可以抛出 generator 的错误
  19. // 输出顺序:
  20. // start
  21. // { value: 'foo', done: false }
  22. // bar // 这里输出 bar 是因为 generator 的第2个 next 传入了一个值给了 foo 函数里的 res 变量,如果没有传入值这里就是输出 undefined
  23. // { value: 'zce', done: false }
  24. // { value: undefined, done: true }

使用 Generator 配合 Promise,用递归实现依次执行,直到 generator 返回的对象的 done 属性为 true,就停止递归,整个 Generator 函数执行完毕。

  1. // Generator 配合 Promise 的异步方案
  2. function ajax (url) {
  3. return new Promise((resolve, reject) => {
  4. var xhr = new XMLHttpRequest()
  5. xhr.open('GET', url)
  6. xhr.responseType = 'json'
  7. xhr.onload = () => {
  8. if (xhr.status === 200) {
  9. resolve(xhr.response)
  10. } else {
  11. reject(new Error(xhr.statusText))
  12. }
  13. }
  14. xhr.send()
  15. })
  16. }
  17. function * main () {
  18. try {
  19. const users = yield ajax('/api/users.json')
  20. console.log(users)
  21. const posts = yield ajax('/api/posts.json')
  22. console.log(posts)
  23. const urls = yield ajax('/api/urls11.json')
  24. console.log(urls)
  25. } catch (e) {
  26. console.log(e)
  27. }
  28. }
  29. // 执行器函数
  30. function co (generator) {
  31. const g = generator()
  32. function handleResult (result) {
  33. if (result.done) return // 生成器函数结束
  34. result.value.then(data => {
  35. handleResult(g.next(data))
  36. }, error => {
  37. g.throw(error)
  38. })
  39. }
  40. handleResult(g.next())
  41. }
  42. co(main)

Async 函数

ES2017 中加入了 Async / Await 语法糖,提供了语言层面的异步编程标准。

  1. // Async / Await 语法糖
  2. function ajax (url) {
  3. return new Promise((resolve, reject) => {
  4. var xhr = new XMLHttpRequest()
  5. xhr.open('GET', url)
  6. xhr.responseType = 'json'
  7. xhr.onload = () => {
  8. if (xhr.status === 200) {
  9. resolve(xhr.response)
  10. } else {
  11. reject(new Error(xhr.statusText))
  12. }
  13. }
  14. xhr.send()
  15. })
  16. }
  17. async function main () {
  18. try {
  19. const users = await ajax('/api/users.json')
  20. console.log(users)
  21. const posts = await ajax('/api/posts.json')
  22. console.log(posts)
  23. const urls = await ajax('/api/urls.json')
  24. console.log(urls)
  25. } catch (e) {
  26. console.log(e)
  27. }
  28. }
  29. const promise = main() // async 函数返回一个 Promise 对象
  30. promise.then(() => {
  31. console.log('all completed')
  32. })