javascript-Promises
使用PromisePromises 是一种 JavaScript 中处理异步代码的方法,无需在代码中编写太多回调。
Promises 通常定义为最终可用的值的代理(a proxy for a value that will eventually become available)。
Promises 是处理异步代码的一种方法,无需在代码中编写太多回调。
其实 Promises 已经存在多年,但是直到 ES2015 才被标准化和引入,现在它们已经在 ES2017 中被 async(异步) 函数所取代。
Async functions(异步函数) 使用 promises API 作为构建块,因此理解 Promises 是必须的,即使在较新的代码中,你可能会使用 async(异步) 函数而不是 promises 。
简而言之,Promsie 是如何工作的
一旦调用了一个 promise ,它就会以 pending(挂起) 状态开始。 这意味着调用者函数继续执行,同时等待 promise 执行自己的处理,并为调用者函数提供一些反馈。
此时,调用者函数等待它以 resolved 状态或者 rejected 状态 返回 promise,但是如果你知道 JavaScript 是异步的,那么 函数会在 promise 完成其工作时继续执行 。
哪些 JS API 使用 promises ?
除了你自己的代码和库代码之外,Promises 还被用于标准的现代 Web API,例如:
- Battery API
- Fetch API
- Service Workers
在现代 JavaScript 中你不太可能发现自己没有使用 Promises ,所以让我们开始深入了解它们。
创建 promise
Promise API 公开了一个 Promise 构造函数,你可以使用 new Promise() 进行初始化:
JavaScript 代码:
1 | let done = true; |
高阶函数, 函数当参数
!注意, 这个executor不是用return, 而是返回并执行resolve, reject回调函数
!注意, 这个executor不是用return, 而是返回并执行resolve, reject回调函数
!注意, 这个executor不是用return, 而是返回并执行resolve, reject回调函数
正如你所看到的,promise 会检查 done 这个全局常量,如果 done 为 true ,我们将返回 resolved 状态的 promise ,否则将返回 rejected 状态的 promise 。
使用 resolve 和 reject 时,我们可以回传一个值,在上面的例子中我们只是回传了一个字符串,但它也可以是一个对象。
使用 promise
在上一节中,我们介绍了如何创建 promise。
现在让我们看看如何使用 promise 。
JavaScript 代码:
1 | const isItDoneYet = new Promise(); |
运行 checkIfItsDone() 将执行 isItDoneYet() promise 并使用 then 回调等待该 promise 的 resolve 状态。如果有错误,它将在 catch 回调中处理这个错误。
链式调用 promises
promise 可以返回另一个 promise ,形成一个链式 promise 。
链式调用 promises 的一个很好的例子是 Fetch API,它是 XMLHttpRequest API 上层 API,我们可以使用它来获取资源,并在获取资源时对 Promise 链进行排列。
promise和ajax没有关系, 而ajax是fetch的基础
Fetch API 基于 promise 机制,调用 fetch() 等同于我们通过 new promise() 定义一个我们自己的 promise。
链式调用 promises 例子
JavaScript 代码:
1 | const status = (response) => { |
在这个例子中,我们调用 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:
JavaScript 代码:
1 | .then((data) => { |
在控制台会打印出这些内容。
处理错误
在上面的例子中,promises 链后面有一个额外的 catch 块。当 promises 链有任何错误发生,将 promise 置为 reject ,控制会转到链中最近的 catch() 语句。
JavaScript 代码:
1 | new Promise((resolve, reject) => { |
级联错误
如果在 catch() 内部又抛出一个错误,你可以添加第二个 catch() 处理它,以此类推。
JavaScript 代码:
1 | new Promise((resolve, reject) => { |
Promise.prototype.finally()
这是 ES2018(ES9)的新特性.
当一个 promise 得到满足(fulfilled)时,它会一个接一个地调用 then() 方法。如果在此期间发生错误 reject,则跳过 then() 方法并执行 catch() 方法。
finally() 允许您运行一些代码,无论 promise 的执行成功或失败:
JavaScript 代码:
1 | fetch('file.json') |
协调 promises
Promise.all()
如果你需要同步处理多个 promises,Promise.all() 可以帮助你定义一组 promises 列表,等待它们全部 resolved 之后再执行某些操作。比如:
JavaScript 代码:
1 | const f1 = fetch('/something.json') |
ES2015 的解构语法也允许你这么做:
JavaScript 代码:
1 | Promise.all([f1, f2]).then(([res1, res2]) => { // 这里解构数组 |
这不仅限于 fetch,任何 promise 都可以处理。
Promise.race()
Promise.race() 会在你传递给它的第一个 promise resolves 时运行,并且它只运行一次附加的回调,并传入首先 resolved 的 promise 返回的结果。
示例:
JavaScript 代码:
1 | const first = new Promise((resolve, reject) => { |
常见错误
Uncaught TypeError: undefined is not a promise
如果你控制台中看到 Uncaught TypeError: undefined is not a promise 错误,请确保使用的是 new Promise() 而不是 Promise()。
如果你还有什么疑问,可以查看 ES6 Promise 指南 666666
例子
1 | const myPromise = new Promise((resolve, reject) => { |
在上面的例子中还有一些需要注意的 重要事项。
我们创建了一个 promise 实例 myPromise。我们分别在第 13 行和第 16 行附加了两个 .then 的处理程序。尽管它们在功能上是相同的,但它们还是被被视为不同的处理程序。但是 ——
- 一个
promise只能成功(resolved)或失败(reject)一次。它不能成功或失败两次,也不能从成功切换到失败,反之亦然。 - 如果一个
promise在你添加成功/失败回调(即 .then)之前就已经成功或者失败,则promise还是会正确地调用回调函数,即使事件发生地比添加回调函数要早。
这意味着一旦 promise 达到最终状态,即使你多次附加 .then 处理程序,状态也不会改变(即不会再重新开始计算)。
这说的就是第二个
.then只会打印Hello, Promises!
为了验证这一点,你可以在第 3 行看到一个 console.log 语句。当你用 .then 处理程序运行上述代码时,需要输出的语句只会被打印一次。 它表明 promise 缓存了结果,并且下次也会得到相同的结果。
另一个要注意的是,promise 的特点是及早求值(evaluated eagerly)。 只要声明并将其绑定到变量,就立即开始执行。没有 .start 或 .begin 方法。就像在上面的例子中那样。
为了确保 promise 不是立即开始而是惰性求值(evaluates lazily), 我们将它们包装在函数中。稍后会看到一个例子。
一个
.catch本身总是被解析为promise,并且不会被拒绝(除非你故意抛出错误)。这就是为什么.then后面的.catch会被执行的原因。
这里建议使用 .catch 而不是带有 onResolved 和 onRejected 参数的 .then 去处理。下面有一个案例解释了为什么最好这样做
1 | const promiseThatResolves = () => |
参考
理解 JavaScript 中的 Promises – JavaScript 完全手册(2018 版)
ES6 Promise 指南 666
ES2017 新特性:Async Functions (异步函数) 66 讲了和 generator 关系
用 async 和 await 编写现代 JavaScript 异步代码 – JavaScript 完全手册(2018 版)
译】Async-Await≈Generators+Promises