Node.js 调试指南

 主页   资讯   文章   代码   电子书 

如何写出清晰优雅的代码也是调试重要的一部分,而在过去很长一段时间内,JavaScript 最令人吐槽的就是回调地狱(callback hell)了。先看一段代码:

step1(function  (err, value1) {
  if (err) {
    ...
    return
  }
  step2(value1, function (err, value2) {
    if (err) {
      ...
      return
    }
    step3(value2, function (err, value3) {
      if (err) {
        ...
        return
      }
      // Do something with value3
    })
  })
})

上面代码依次执行 step1、step2、step3,且后一个函数用到了前一个函数执行的结果。这只是一个简单的例子,真实环境下可能会写出嵌套更深的回调函数,代码形成一个倒金字塔。如果使用 Promise,代码就优雅很多了,如下所示:

step1()
  .then(step2)
  .then(step3)
  .catch((e) => {
    // Do something with error
  })

Promise 的出现就是为了解决回调地狱的问题,它最早是由社区提出和实现的,衍生的规范也有很多,最终 ES6 采用了 Promise/A+ 规范,并将其写进了语言标准,统一了用法。

3.1.1 Promise/A+ 规范

Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,细节各有不同,最终 ES6 中采用了 Promise/A+ 规范。在讲解 Promise 实现之前,当然要先了解 Promise/A+ 规范,Promise/A+ 规范参考:

  • 英文版:https://promisesaplus.com/
  • 中文版:http://www.ituring.com.cn/article/66566

规范虽然不长,但细节也比较多,笔者挑出几个要点简单说明一下:

  1. Promise 本质是一个状态机。每个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。
  2. then 方法可以被同一个 promise 调用多次。
  3. then 方法必须返回一个 promise,从而可以实现链式调用。
  4. 值穿透。下面会讲。

Promise 的 API 并不多,但是 Promise 并不简单,如何彻底理解并玩转 Promise 呢?当然是从头实现一遍 Promise 啦。我们假设读者已经熟悉了 Promise 的基本用法,本节内容分为两部分:第一部分讲解如何从零开始实现一个 Promise,第二部分通过十道题巩固读者对 Promise 的理解。

3.1.2 从零开始实现 Promise

我们知道 Promise 是本质是一个构造函数,需要用 new 调用,并有以下几个 api:

function Promise (resolver) {}

Promise.prototype.then = function () {}
Promise.prototype.catch = function () {}

Promise.resolve = function () {}
Promise.reject = function () {}
Promise.all = function () {}
Promise.race = function () {}

创建以下初始代码,然后开始一步一步构建完整的 Promise 实现。如下所示:

function INTERNAL () {}
function isFunction (func) {
  return typeof func === 'function'
}
function isObject (obj) {
  return typeof obj === 'object'
}
function isArray (arr) {
  return Array.isArray(arr)
}

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

module.exports = Promise

function Promise (resolver) {
  if (!isFunction(resolver)) {
    throw new TypeError('resolver must be a function')
  }
  this.state = PENDING
  this.value = void 0
  this.queue = []
  if (resolver !== INTERNAL) {
    safelyResolveThen(this, resolver)
  }
}

注意:以下 promise 均指代 Promise 实例。

INTERNAL 就是一个空函数,后面会用来传入 Promise 构造函数生成一个 promise 实例。定义了 3 个辅助函数:isFunction、isObject 和 isArray。定义了 3 种状态:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面会讲。promise 内部有三个变量:

  1. state:当前 promise 的状态,初始值为 PENDING。状态改变只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
  2. value:初始值是 void 0(即 undefined),当 state 是 FULFILLED 时存储返回值,当 state 是 REJECTED 时存储错误。
  3. queue:promise 内部的回调队列,后面会讲它的作用。

3.1.3 Promise 实现原理

笔者发布了一个 Promise/A+ 规范实现的模块——appoint,我们拿这个模块研究一下它是如何实现 Promise 的。看一段代码:

const Promise = require('appoint')
const promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
const a = promise.then(function onSuccess () {})
const b = promise.catch(function onError () {})
console.dir(promise, { depth: 10 })
console.log(promise.queue[0].promise === a)
console.log(promise.queue[1].promise === b)

运行后打印出:

Promise {
  state: 'pending',
  value: undefined,
  queue:
   [ QueueItem {
       promise: Promise { state: 'pending', value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] },
     QueueItem {
       promise: Promise { state: 'pending', value: undefined, queue: [] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }
true
true

注意:原生 Promise 是没有 queue 属性的,appoint 的实现中添加了这个属性。

可以看出,queue 数组中有两个对象。因为规范中规定:then 方法可以被同一个 promise 调用多次。上例中在调用 .then 和 .catch 时 promise 并没有被 resolve,所以将 .then 和 .catch 生成的新 promise(a 和 b) 和正确时的回调(onSuccess 包装成 callFulfilled)和错误时的回调(onError 包装成 callRejected)生成一个 QueueItem 实例并 push 到 queue 数组里,所以两个 console.log 都打印 true。当 promise 状态改变时遍历内部 queue 数组,统一执行成功(callFulfilled)或失败(callRejected)的回调(传入 promise 的 value 值),生成的结果分别设置 a 和 b 的 state 和 value,这就是 Promise 实现的基本原理。 再来看另一个例子:

const Promise = require('appoint')
const promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then(() => {})
  .then(() => {})
  .then(() => {})
console.dir(promise, { depth: 10 })

打印出:

Promise {
  state: 'pending',
  value: undefined,
  queue:
   [ QueueItem {
       promise:
        Promise {
          state: 'pending',
          value: undefined,
          queue:
           [ QueueItem {
               promise:
                Promise {
                  state: 'pending',
                  value: undefined,
                  queue:
                   [ QueueItem {
                       promise: Promise { state: 'pending', value: undefined, queue: [] },
                       callFulfilled: [Function],
                       callRejected: [Function] } ] },
               callFulfilled: [Function],
               callRejected: [Function] } ] },
       callFulfilled: [Function],
       callRejected: [Function] } ] }

链式调用了 3 次 .then,每次调用 .then 将它生成的 promise 放到了调用它的 promise 队列里,形成了 3 层调用关系。当最外层的 promise 状态改变时,遍历它的 queue 数组调用对应的回调,设置子 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调......以此类推。

注意:这里 queue 是嵌套的,而不是像上个例子中 queue 是平铺的。

3.1.4 safelyResolveThen

接下来完成 safelyResolveThen 的逻辑,代码如下:

function safelyResolveThen (self, then) {
  let called = false
  try {
    then(function (value) {
      if (called) {
        return
      }
      called = true
      doResolve(self, value)
    }, function (error) {
      if (called) {
        return
      }
      called = true
      doReject(self, error)
    })
  } catch (error) {
    if (called) {
      return
    }
    called = true
    doReject(self, error)
  }
}

safelyResolveThen 顾名思义用来 “安全的执行 then 函数”,这里的 then 函数指 “第一个参数是 resolve 函数第二个参数是 reject 函数的函数”,适用于以下两种情况:

  1. 构造函数的参数,即这里的 resolver:
  new Promise(function resolver (resolve, reject) {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
  })
  1. promise 的 then:
  promise.then(resolve, reject)

safelyResolveThen 有 3 个作用:

  1. try...catch 用来捕获函数内抛出的异常,如构造函数内抛出异常:
  new Promise(function resolver (resolve, reject) {
    throw new Error('Oops')
  })
  1. called 控制 resolve 或 reject 只执行一次,多次调用没有任何作用。即:
  const Promise = require('appoint')
  const promise = new Promise(function resolver (resolve, reject) {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
    reject('error')
  })
  promise.then(console.log)
  promise.catch(console.error)

打印 error,不会再打印 haha。

  1. 没有错误则执行 doResolve,有错误则执行 doReject。

3.1.5 doResolve 和 doReject

doResolve 和 doReject 相关代码如下:

function doResolve (self, value) {
  try {
    const then = getThen(value)
    if (then) {
      safelyResolveThen(self, then)
    } else {
      self.state = FULFILLED
      self.value = value
      self.queue.forEach(function (queueItem) {
        queueItem.callFulfilled(value)
      })
    }
    return self
  } catch (error) {
    return doReject(self, error)
  }
}

function doReject (self, error) {
  self.state = REJECTED
  self.value = error
  self.queue.forEach(function (queueItem) {
    queueItem.callRejected(error)
  })
  return self
}

doReject 用来设置 promise 的 state 为 REJECTED,value 为 error,然后遍历 queue,设置所有子 promise 的状态为 REJECTED 和值为 error。doResolve 结合 safelyResolveThen 使用不断地解包 promise,直至返回值是非 promise 对象后,设置 promise 的状态和值,然后设置子 promise 的状态和值。

这里有个辅助函数 getThen:

function getThen (promise) {
  const then = promise && promise.then
  if (promise && (isObject(promise) || isFunction(promise)) && isFunction(then)) {
    return function applyThen () {
      then.apply(promise, arguments)
    }
  }
}

getThen 实现了规范中规定的:如果 then 是函数,将 x(即被调用的 promise) 作为函数的 this 调用。

3.1.6 Promise.prototype.then 和 Promise.prototype.catch

接下来实现 Promise.prototype.then 和 Promise.prototype.catch,代码如下:

Promise.prototype.then = function (onFulfilled, onRejected) {
  if ((!isFunction(onFulfilled) && this.state === FULFILLED) ||
    (!isFunction(onRejected) && this.state === REJECTED)) {
    return this
  }
  const promise = new this.constructor(INTERNAL)
  if (this.state !== PENDING) {
    const resolver = this.state === FULFILLED ? onFulfilled : onRejected
    unwrap(promise, resolver, this.value)
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected))
  }
  return promise
}

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected)
}

上述代码中的 return this 实现了值穿透,后面会细讲。可以看出,then 方法中生成了一个新的 promise 然后返回。如果 promise 的状态改变了,则调用 unwrap,否则将生成的 promise 加入到当前 promise 的回调队列 queue 里,之前已经讲解了如何消费 queue。有 3 点需要讲解:

  1. Promise 构造函数传入了一个 INTERNAL 空函数,因为这个新产生的 promise 可以认为是内部的 promise,需要根据外部的 promise 的状态和值产生自身的状态和值,不需要传入回调函数,而外部 Promise 需要传入回调函数决定它的状态和值,所以之前 Promise 的构造函数里做了判断区分外部调用还是内部调用:
  if (resolver !== INTERNAL) {
    safelyResolveThen(this, resolver)
  }
  1. QueueItem 代码如下:
  function QueueItem (promise, onFulfilled, onRejected) {
    this.promise = promise
    this.callFulfilled = function (value) {
      doResolve(this.promise, value)
    }
    this.callRejected = function (error) {
      doReject(this.promise, error)
    }
    if (isFunction(onFulfilled)) {
      this.callFulfilled = function (value) {
        unwrap(this.promise, onFulfilled, value)
      }
    }
    if (isFunction(onRejected)) {
      this.callRejected = function (error) {
        unwrap(this.promise, onRejected, error)
      }
    }
  }

promise 为 then 生成的新 promise,onFulfilled 和 onRejected 即是 then 参数中的 onFulfilled 和 onRejected。从上面代码可以看出:当 promise 状态变为 FULFILLED 时,之前注册的 then 函数通过 callFulfilled 调用 unwrap 进行解包最终得出 promise 的状态和值;之前注册的 catch 函数,用 callRejected 直接调用 doReject,设置队列里 promise 的状态和值。当 promise 状态变为 REJECTED 类似。

  1. unwrap 代码如下:
  function unwrap (promise, func, value) {
    process.nextTick(function () {
      let returnValue
      try {
        returnValue = func(value)
      } catch (error) {
        return doReject(promise, error)
      }
      if (returnValue === promise) {
        doReject(promise, new TypeError('Cannot resolve promise with itself'))
      } else {
        doResolve(promise, returnValue)
      }
    })
  }

unwrap 函数从名字也可以看出是用来解包的,即拿到父 promise 的结果设置当前 promise 的状态和值。第一个参数是 promise,第二个参数是父 promise 的 then 的回调(onFulfilled/onRejected),第三个参数是父 promise 的值(正常值/错误)。有 3 点需要说明:

  1. 使用 process.nextTick 将代码异步执行,这也是规范里明确规定的。看一段代码:
  const Promise = require('appoint')
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
  })
  promise.then(() => {
    promise.then(() => {
      console.log('1')
    })
    console.log('2')
  })

打印 2 1,去掉 process.nextTick 则打印 1 2。

  1. try...catch 用来捕获 then/catch 函数内抛出的异常,并调用 doReject,如:
  promise.then(() => {
    throw new Error('haha')
  })
  promise.catch(() => {
    throw new Error('haha')
  })
  1. 返回的值不能是 promise 本身,否则会造成死循环,如下代码:
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
  })
  const a = promise.then(() => {
    return a
  })

  a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]

注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的语法糖。

至此,Promise 的核心部分就实现完了。

3.1.7 值穿透

上面提到过好几次值穿透,什么是值穿透呢?上面的 Promise.prototype.then 的实现中有这么一段代码:

Promise.prototype.then = function (onFulfilled, onRejected) {
  if ((!isFunction(onFulfilled) && this.state === FULFILLED) ||
    (!isFunction(onRejected) && this.state === REJECTED)) {
    return this
  }
  ...
};

值穿透即传入 then/catch 的参数如果不为函数,则忽略该值,返回上一个 promise 的结果。看一段代码:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('haha')
  }, 1000)
})
promise
  .then('hehe')
  .then(console.log)

最终打印 haha 而不是 hehe。

通过 return this 只实现了值穿透的一种情况,其实值穿透有两种情况:

  1. promise 已经是 FULFILLED/REJECTED 时,通过 return this 实现的值穿透:
  const Promise = require('appoint')
  const promise = new Promise(function (resolve) {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
  })
  promise.then(() => {
    promise.then().then((res) => {// (1)
      console.log(res)// haha
    })
    promise.catch().then((res) => {// (2)
      console.log(res)// haha
    })
    console.log(promise.then() === promise.catch())// true
    console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true
  })

上述代码 (1)、(2) 处 promise 已经是 FULFILLED 了符合条件所以执行了 return this

  1. promise 是 PENDING 时,通过生成新的 promise 加入到父 promise 的 queue,父 promise 状态改变时调用 callFulfilled->doResolve 或 callRejected->doReject(因为 then/catch 传入的参数不是函数)设置子 promise 的状态和值为父 promise 的状态和值。看一段代码:
  const Promise = require('appoint')
  const promise = new Promise((resolve) => {
    setTimeout(() => {
      resolve('haha')
    }, 1000)
  })
  const a = promise.then()
  a.then((res) => {
    console.log(res)// haha
  })
  const b = promise.catch()
  b.then((res) => {
    console.log(res)// haha
  })
  console.log(a === b)// false

3.1.8 Promise.resolve 和 Promise.reject

Promise.resolve 和 Promise.reject 是 Promise 的两个静态方法,用来快捷的生成一个状态为 fulfilled 或者 rejected 的 promise 实例。代码如下:

Promise.resolve = resolve
function resolve (value) {
  if (value instanceof this) {
    return value
  }
  return doResolve(new this(INTERNAL), value)
}

Promise.reject = reject
function reject (reason) {
  return doReject(new this(INTERNAL), reason)
}

当 Promise.resolve 参数是一个 promise 时,直接返回该值。

3.1.9 Promise.all

Promise.all 接收一个数组,用来并行执行一组 promise。代码如下:

Promise.all = all
function all (iterable) {
  const self = this
  if (!isArray(iterable)) {
    return this.reject(new TypeError('must be an array'))
  }

  const len = iterable.length
  let called = false
  if (!len) {
    return this.resolve([])
  }

  const values = new Array(len)
  let resolved = 0
  let i = -1
  const promise = new this(INTERNAL)

  while (++i < len) {
    allResolver(iterable[i], i)
  }
  return promise
  function allResolver (value, i) {
    self.resolve(value).then(resolveFromAll, function (error) {
      if (!called) {
        called = true
        doReject(promise, error)
      }
    })
    function resolveFromAll (outValue) {
      values[i] = outValue
      if (++resolved === len && !called) {
        called = true
        doResolve(promise, values)
      }
    }
  }
}

Promise.all 用来并行执行多个 promise/值,当所有 promise/值执行完毕或有一个 promise 状态变为 rejected 时返回。以上代码可以看出:

  1. Promise.all 内部生成了一个新的 promise 返回。
  2. called 用来控制即使有多个 promise rejected 也只有第一个生效。
  3. values 用来存储执行结果。
  4. 当最后一个 promise 状态改变后,使用 doResolve(promise, values) 设置 promise 的 state 为 FULFILLED,value 为结果数组 values。

3.1.10 Promise.race

Promise.race 接收一个数组,当数组中有一个 promise 状态发生改变( pending -> fulfilled/rejected)时返回。

Promise.race = race
function race (iterable) {
  const self = this
  if (!isArray(iterable)) {
    return this.reject(new TypeError('must be an array'))
  }

  const len = iterable.length
  let called = false
  if (!len) {
    return this.resolve([])
  }

  let i = -1
  const promise = new this(INTERNAL)

  while (++i < len) {
    resolver(iterable[i])
  }
  return promise
  function resolver (value) {
    self.resolve(value).then(function (response) {
      if (!called) {
        called = true
        doResolve(promise, response)
      }
    }, function (error) {
      if (!called) {
        called = true
        doReject(promise, error)
      }
    })
  }
}

Promise.race 与 Promise.all 代码相近,只不过这里用 called 控制只要有任何一个 promise 状态改变则立即去设置返回的 promise 的状态和值。

至此,Promise 的实现全部讲解完毕。

3.1.11 十道题

现在,我们以十道题巩固一下前面所学到的 Promise 的知识点。

题目一

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)

运行结果:

1
2
4
3

解释:Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

题目二

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

运行结果:

promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }

解释:promise 有 3 种状态:pending、fulfilled 或 rejected。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面的 promise2 并不是 promise1,而是返回的一个新的 Promise 实例。

题目三

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果:

then: success1

解释:构造函数中的 resolve 或 reject 只有在第 1 次执行时有效,多次调用没有任何作用,再次印证代码二的结论:promise 状态一旦改变则不能再变。

再看两个例子:

const promise = new Promise((resolve, reject) => {
  console.log(1)
  return Promise.reject(new Error('haha'))
})
promise.then((res) => {
  console.log(2, res)
}).catch((err) => {
  console.error(3, err)
})
console.log(4)
console.log(promise)

运行结果:

1
4
Promise { <pending> }
(node:22493) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: haha
(node:22493) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
const promise = new Promise((resolve, reject) => {
  console.log(1)
  throw new Error('haha')
})
promise.then((res) => {
  console.log(2, res)
}).catch((err) => {
  console.error(3, err)
})
console.log(4)
console.log(promise)

运行结果:

1
4
Promise {
  <rejected> Error: haha
    at Promise (/Users/nswbmw/Desktop/test/app.js:6:9)
    ...
3 Error: haha
    at Promise (/Users/nswbmw/Desktop/test/app.js:6:9)
    ...

解释:构造函数内只能通过调用 resolve(pending->fullfiled) 或者 reject(pending->rejected) 或者 throw 一个 error(pending->rejected) 改变状态。所以第一个例子的 promise 状态是 pending,也就不会调用 .then/.catch。

题目四

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

运行结果:

1
2

解释:promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 在每次调用 .then 或者 .catch 时都会返回一个新的 promise,从而可以实现链式调用。

题目五

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})

const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})

运行结果:

once
success 1005
success 1007

解释:promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说,promise 内部状态一经改变,并且有了一个值,则后续在每次调用 .then 或者 .catch 时都会直接拿到该值。

题目六

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果:

then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

解释:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成如下其中一种:

  1. return Promise.reject(new Error('error!!!'))
  2. throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))

题目七

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

运行结果:

TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3

解释:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。类似于:

process.nextTick(function tick () {
  console.log('tick')
  process.nextTick(tick)
})

题目八

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

运行结果:

1

解释:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

题目九

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })

运行结果:

fail2: Error: error
    at success (...)
    at ...

解释:.then 可以接收两个参数,第 1 个是处理成功的函数,第 2 个是处理错误的函数。.catch 是 .then 第 2 个参数的简便写法,但是在用法上有一点需要注意:.then 的第 2 个处理错误的函数(fail1)捕获不了第 1 个处理成功的函数(success)抛出的错误,而后续的 .catch 方法(fail2)可以捕获之前的错误。当然,以下代码也可以:

Promise.resolve()
  .then(function success1 (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .then(function success2 (res) {
  }, function fail2 (e) {
    console.error('fail2: ', e)
  })

题目十

Promise.resolve()
  .then(() => {
    console.log('then')
  })
process.nextTick(() => {
  console.log('nextTick')
})
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')

运行结果:

end
nextTick
then
setImmediate

解释:process.nextTick 和 promise.then 都属于 microtask(但 process.nextTick 的优先级大于 promise.then),而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,以上代码本身(macrotask)在执行完后会执行一次 microtask。

3.1.12 参考链接

  • http://es6.ruanyifeng.com/#docs/promise
  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
  • https://promisesaplus.com/

上一节:2.4 cpu-memory-monitor

下一节:3.2 Async + Await