JavaScript中的Promise和Async/Await
Promise是ES6,async/await是ES7确定的ECMA标准.目前最新的 Chrome浏览器和Typescript已经支持.
Promise和async/await极大的改善了JS的编码体验.本文将介绍它们使用方式和注意事项.
Promise
Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值.
它的基础语法:
1 | new Promise( function(resolve, reject) {...} /* executor */ ); |
Promise基本的使用方式是用它构建一个Promise实例. 它接受一个函数作为参数,参数默认会传递两个值:reslove和reject.这个两个参数是用来修改Promise的状态.
Promise只有三种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
⚠️fulfilled经常被 resolve代替.
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。
Promise状态转换过程如下:

⚠️ Promise.prototype.then 、Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。
一个实例后的Promise可以通过Promise.prototype.then或Promise.prototype.catch获取到异步执行的结果
Promise原型
Promise.prototype.constructor
返回被创建的实例函数. 默认为 Promise 函数.
Promise.prototype.catch(onRejected)
添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果.
Promise.prototype.then(onFulfilled, onRejected)
添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来resolve.
Promise.prototype.finally(onFinally)
添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)
Promise方法
Promise.all(iterable)
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | const p = Promise.all([p1, p2, p3]); |
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.allSettled(iterable)
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。返回一个promise,该promise在所有promise完成后完成。并带有一个对象数组,每个对象对应每个promise的结果。
1 | const promises = [ |
上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
Promise.any(iterable)
Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected`状态。
1 | const promises = [ |
上面代码中,Promise.any()方法的参数数组包含三个 Promise 操作。其中只要有一个变成fulfilled,Promise.any()返回的 Promise 对象就变成fulfilled。如果所有三个操作都变成rejected,那么await命令就会抛出错误。
Promise.race(iterable)
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | const p = Promise.race([p1, p2, p3]); |
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.reject(reason)
返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
1 | const p = Promise.reject('出错了'); |
Promise.resolve(value)
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。
1 | Promise.resolve('foo') |
手写 Promise

同时,需要在 myPromise的原型上定义链式调用的 then方法:

⚠️根据上小节介绍的.应该原型上应该还需要实现catch方法.并且then和catch应该返回一个Promise实例
另一个较完整版本:
1 | const PENDING ="pending"; |
async/await
async/await 简单来说,它们是基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码
async
一个async修饰的函数,它会自动返回一个Promise实例
1 | function hello() { return "Hello" }; |
Promise实例是通过then获取异步返回值的.所以可以这么写:
1 | hello().then((value) => console.log(value)) |
将 async 关键字加到函数申明中,可以告诉它们返回的是 promise,而不是直接返回值。此外,它避免了同步函数为支持使用 await 带来的任何潜在开销。在函数声明为 async 时,JavaScript引擎会添加必要的处理,以优化你的程序。
await
当 await 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。
⚠️您可以在调用任何返回Promise的函数时使用 await,包括Web API函数。
1 | async function hello() { |
利用async/await重写Promise
使用Promise写的异步:
1 | fetch('coffee.jpg') |
看起来和 使用回掉函数处理异步的写法很相像.
使用async/await写Promise异步:
1 | async function myFetch() { |
myFetch内部看起来就像写同步函数一样.它使代码简单多了更容易理解 —— 去除了到处都是 .then() 代码块!
async/await处理异常
Promise的reject状态是无法在 await获取到的. 正确的处理方式是,你应该用try...catch来处理可能出现的异常
1 | async function myFetch() { |
或者在外部函数 添加catch方法
1 | async function myFetch() { |
这是因为 .catch() 块将捕获来自异步函数调用和promise链中的错误。如果您在此处使用了try/catch 代码块,则在调用 myFetch() 函数时,您仍可能会收到未处理的错误。
async/await的缺陷
Async/await 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。
这意味着您的代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。
作者: Fynn
链接: https://fynn90.github.io/2018/12/12/Javascript%E4%B8%AD%E7%9A%84await%E5%92%8CPromise/
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可