理解 JavaScript 中的 Promises – JavaScript 完全手册(2018版)

10年服务1亿前端开发工程师

小编推荐:掘金是一个面向程序员的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。

注:本文为 《 JavaScript 完全手册(2018版) 》第25节,你可以查看该手册的完整目录。

Promises 是一种 JavaScript 中处理异步代码的方法,无需在代码中编写太多回调。

Promises 通常定义为最终可用的值的代理(a proxy for a value that will eventually become available)。

Promises 是处理异步代码的一种方法,无需在代码中编写太多回调。

其实 Promises 已经存在多年,但是直到 ES2015 才被标准化和引入,现在它们已经在 ES2017 中被 async(异步) 函数所取代。

Async functions(异步函数) 使用 promises API 作为构建块,因此理解 Promises 是必须的,即使在较新的代码中,你可能会使用 async(异步) 函数而不是promises 。

简而言之,承诺是如何工作的

一旦调用了一个 promise ,它就会以 pending(挂起) 状态开始。 这意味着调用者函数继续执行,同时等待 promise 执行自己的处理,并为调用者函数提供一些反馈。

此时,调用者函数等待它以 resolved 状态或者 rejected 状态 返回 promise,但是如果你知道 JavaScript 是异步的,那么 函数会在 promise 完成其工作时继续执行

哪些 JS API使用 promises ?

除了你自己的代码和库代码之外,Promises 还被用于标准的现代 Web API,例如:

在现代 JavaScript 中你不太可能发现自己没有使用 Promises ,所以让我们开始深入了解它们。

创建 promise

Promise API 公开了一个 Promise 构造函数,你可以使用 new Promise() 进行初始化:

let done = true
const isItDoneYet = new Promise(
  (resolve, reject) => {
    if (done) {
      const workDone = 'Here is the thing I built'
      resolve(workDone)
    } else {
      const why = 'Still working on something else'
      reject(why)
    }
  }
)

正如你所看到的,promise 会检查 done 这个全局常量,如果 donetrue ,我们将返回 resolved 状态的 promise ,否则将返回 rejected 状态的 promise 。

使用 resolvereject 时,我们可以回传一个值,在上面的例子中我们只是回传了一个字符串,但它也可以是一个对象。

使用 promise

在上一节中,我们介绍了如何创建 promise 。

现在让我们看看如何使用 promise 。

const isItDoneYet = new Promise(
  //...
)
const checkIfItsDone = () => {
  isItDoneYet
    .then((ok) => {
      console.log(ok)
    })
    .catch((err) => {
      console.error(err)
    })
}

运行 checkIfItsDone() 将执行 isItDoneYet() promise 并使用 then 回调等待该 promise 的 resolve 状态。如果有错误,它将在 catch 回调中处理这个错误。

链式调用 promises

promise 可以返回另一个 promise,形成一个链式 promise。

链式调用 promises 的一个很好的例子是 Fetch API,它是XMLHttpRequest API 上层 API,我们可以使用它来获取资源,并在获取资源时对 Promise 链进行排列。

Fetch API 基于 promise 机制,调用 fetch() 等同于我们通过 new promise() 定义一个我们自己的 promise。

链式调用 promises 例子

const status = (response) => {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  }
  return Promise.reject(new Error(response.statusText))
}

const json = (response) => response.json()
fetch('/todos.json')
  .then(status)
  .then(json)
  .then((data) => { console.log('Request succeeded with JSON response', data) })
  .catch((error) => { console.log('Request failed', error) })

在这个例子中,我们调用 fetch() 从根域名中的 todos.json 文件获取一个 TODO 清单,我们创建了一个 promises 链。

运行 fetch() 返回一个包含很多个属性的 response,我们引用了其中的:

  • status,数字值表示的 HTTP 状态代码
  • statusText,状态消息,请求成功时为 OK

response 还有一个 json() 方法,该方法返回一个 resolve 状态的 promise,并且将响应内容转化为 JSON 作为该 promise 的回传值。

在这些前提下,会发生这样的情况:链中的第一个 promise 是我们定义的函数 status() ,它检查响应状态,如果响应不成功(在200到299之间),则 reject 该 promise 。

此操作将导致 promise 链跳过队列中的所有的 promise ,并将直接跳到底部的 catch() 语句,打印 Request failed 文本以及错误消息。

如果成功,它会调用我们定义的 json() 函数。 前一个 promise 返回 response 对象,作为第二个 promise 的输入。

在这种情况下,我们返回处理过的 JSON 数据 ,因此第三个 promise 直接接收 JSON:

.then((data) => {
  console.log('Request succeeded with JSON response', data)
})

在控制台会打印出这些内容。

处理错误

在上面的例子中,promises 链后面有一个额外的 catch 块。当 promises 链有任何错误发生,将 promise 置为 reject ,控制会转到链中最近的 catch() 语句。

new Promise((resolve, reject) => {
  throw new Error('Error')
})
  .catch((err) => { console.error(err) })

// or

new Promise((resolve, reject) => {
  reject('Error')
})
  .catch((err) => { console.error(err) })

级联错误

如果在 catch() 内部又抛出一个错误,你可以添加第二个 catch() 处理它,以此类推。

new Promise((resolve, reject) => {
  throw new Error('Error')
})
  .catch((err) => { throw new Error('Error') })
  .catch((err) => { console.error(err) })

Promise.prototype.finally()

这是 ES2018(ES9)的新特性.

当一个 promise 得到满足(fulfilled)时,它会一个接一个地调用 then() 方法。如果在此期间发生错误,则跳过 then() 方法并执行 catch() 方法。

finally() 允许您运行一些代码,无论 promise 的执行成功或失败:

fetch('file.json')
  .then(data => data.json())
  .catch(error => console.error(error))
  .finally(() => console.log('finished'))

协调 promises

Promise.all()

如果你需要同步处理多个 promises,Promise.all() 可以帮助你定义一组 promises 列表,等待它们全部 resolved 之后再执行某些操作。比如:

const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')

Promise.all([f1, f2]).then((res) => {
    console.log('Array of results', res)
})
.catch((err) => {
  console.error(err)
})

ES2015 的解构语法也允许你这么做:

Promise.all([f1, f2]).then(([res1, res2]) => {
    console.log('Results', res1, res2)
})

这不仅限于 fetch,任何 promise 都可以处理。

Promise.race()

Promise.race() 会在你传递给它的第一个 promise resolves 时运行,并且它只运行一次附加的回调,并传入首先 resolved 的 promise 返回的结果。

示例:

const first = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, 'first')
})
const second = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'second')
})

Promise.race([first, second]).then((result) => {
  console.log(result) // second
})

常见错误

Uncaught TypeError: undefined is not a promise

如果你控制台中看到 Uncaught TypeError: undefined is not a promise 错误,请确保使用的是 new Promise() 而不是 Promise()

如果你还有什么疑问,可以查看 ES6 Promise 指南


如果你觉得本文对你有帮助,那就请分享给更多的朋友
关注「前端干货精选」加星星,每天都能获取前端干货
赞(3) 打赏
未经允许不得转载:WEB前端开发 » 理解 JavaScript 中的 Promises – JavaScript 完全手册(2018版)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

前端开发相关广告投放 更专业 更精准

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏