[关闭]
@Dale-Lin 2021-02-24T14:53:29.000000Z 字数 6201 阅读 1023

Promise 与 async/await

深入理解ES6


Promise

Promise 对象用于表示一个异步操作的完成与否,以及返回的值:

  1. let promise = new Promise((resolve, reject) => {
  2. setTimeout(() => resolve('foo');}, 1000);
  3. });
  4. promise.then((value) => {console.log(value);}); //log 'foo' after 1s.

语法

new Promise((resolve, reject) => {/* executor */})

推荐使用箭头函数传入 (resolve, reject)。

executor 在 Promise 构造函数执行时立即调用,resolve、reject 方法将 Promise 对象的状态 pending 改为 fulfilled 或 rejected。

若在 executor 中抛出一个错误,Promise 对象的状态改为 rejected。

resolve() 方法的参数被用于 .then() 第一个参数接收。
reject() 方法的参数被用于 .then() 第二个参数接收。
executor 函数体内的代码是同步立即执行的。

原型方法

Promise.prototype.then(onFulfilled(value), onRejected(reason))
接受两个处理参数,对应 Promise 对象的最终状态回调处理。返回一个新 Promise 对象,对于处理函数的返回值:

  1. var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('66666'); console.log(b)}, 1000)});
  2. var b = a.then((val) => a);
  3. console.log(b);
  4. // Promise {<pending>}
  5. // 1s 后输出:
  6. // Promise {<pending>},此时还在 a 修改状态的函数内;
  7. // 说明 b 是新的 promise 对象,这时还没同步;
  8. // 再打印:
  9. // Promise {<resolved>: "66666"},已经同步
  10. a === b;
  11. // false

Promise.prototype.catch(onRejected(reason))
专门处理 rejected 的 Promise 对象,也可用来捕捉错误。返回一个新 Promise 对象,以处理函数的返回值来 reslove。

Promise.prototype.finally(onFinally)
无论当前 Promise 对象状态,调用处理函数。

.then() 和 .catch() 方法是异步的。

方法

当不确定一个对象是否为 Promise 对象时,可使用 Promise.resolve() 处理。
不要调用 resolve() 自己的 thenable 对象!会导致无限循环!

  1. var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('66666'); console.log(b)}, 1000)});
  2. var b = Promise.resolve(a);
  3. console.log(b)
  4. // Promise {<pending>}
  5. // 1s 后输出
  6. // Promise {<resolved>: "66666"}
  7. a === b
  8. // true

Promise.all(iterable)
返回一个 Promise 对象,当 iterable 内所有 Promise 对象都是 resolved 状态,则 resolved,返回包含对应各 resolved 的 value 的数组;否则 rejected,返回第一个 rejected 的错误信息。

使用

保证

对于 Promise,有以下保证:

链式调用

可以使用 Promise chain 来连续执行两个以上异步操作:

  1. const promise2 = promise1.then(successCallback, failureCallback);

有效避免回调地狱:

  1. doSomething().then(function(result) {
  2. return doSomethingElse(result);
  3. })
  4. .then(function(newResult) {
  5. return doThirdThing(newResult);
  6. })
  7. .then(function(finalResult) {
  8. console.log('Got the final result: ' + finalResult;
  9. return 'next arg.');
  10. })
  11. .catch(failureCallback);

.then() 的参数是可选的, .catch(failureCallback) 方法是 .then(null, failureCallback) 的简写。

建议使用箭头函数:

  1. doSomething()
  2. .then(result => doSomethingElse(result))
  3. .then(newResult => doThirdThing(newResult))
  4. .then(finalResult => {
  5. console.log(`Final result: ${finalResult}`);
  6. })
  7. .catch(failureCallback);

返回值很重要,否则回调无法获得前一个 Promise 的结果。

.catch() 的后续链

在失败后还可以继续链式调用,使用 .catch() 实现:

  1. new Promise((resolve, reject) => {
  2. console.log('Initial');
  3. resolve();
  4. })
  5. .then(() => {
  6. throw new Error('Something failed');
  7. console.log('Do this');
  8. })
  9. .catch(() => console.log('Do that');)
  10. .then(() => {
  11. console.log('Do the last thing');
  12. })

输出如下:

  1. Initial
  2. Do that
  3. Do the last thing

"Do this" 没有显示,因为抛出的错误导致了一个 rejection。

Error 传播

一个 Promise 链在出现异常后停止,向下寻找 .catch() 处理程序。这是模仿同步语法实现的。

在 ES2017 标准的 async/await 中体现地淋漓尽致:

  1. async function foo() {
  2. try {
  3. const result = await doSomething();
  4. const newResult = await doSomethingElse(result);
  5. const finalResult = await doThirdThing(newResult);
  6. } catch(error) {
  7. failureCallback(error);
  8. }
  9. }

这是在 Promise 基础上构建的。

在旧式回调 API 中创建 Promise

理想情况下,所有异步函数都返回 Promise 对象,但有些 API 依旧需要传入成功或失败的回调。最经典的就是 setTimeout() 函数。
混用旧式回调和 Promise 将无法捕获旧式回调中的错误,使用 Promise 包裹。最好将有问题的函数包裹在尽量底层,并且不再直接调用它们:

  1. const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
  2. wait(1000).then(() => saySomething('1 seconds')).catch(failureCallback);

组合

Promise.resolve() 和 Promise.reject() 是主动创建一个已经 resolved 或 rejected 的 Promise 的方法,有时很有用。

Promise.all() 和 Promise.race() 是并列运行异步操作的两个工具:

  1. Promise.all([func1(), func2(), func3()])
  2. .then(([result1, result2, result3])) => {/* some code */});

这样写:

  1. [func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
  2. .then(result3 => {/* use result3 */});

相当于队列链式调用:

  1. Promise.resolve().then(func1).then(func2).then(func3).then((result3) => {/**/});

可抽象成复用的函数:

  1. let applyAsync = (acc, val) => acc.then(val);
  2. let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

composeAsync 可接受任意数量的函数作为参数,然后返回一个可接受一个初始值传入组合传递的新函数:

  1. const transformData = composeAsync(func1, func2, func3);
  2. const result3 = transformData(data);

使用 async/await 语法更加便捷:

  1. let result;
  2. for (const f of [func1, func2, func3]) {
  3. result = await f(result);
  4. }
  5. /* use the last result */

时序

即使一个 Promise 已经是 resolved 状态,也会在当前任务队列结束后调用(异步调用)。

嵌套

简单的 Promise 连最好不要嵌套。
嵌套是一种限制 .catch() 作用域的声明,嵌套的 .catch() 方法只会捕获其作用域及前面的错误。如果正确使用,可以更准确地修复错误:

  1. doSomethingCritical()
  2. .then(result => doSomethingOptional()
  3. .then(optionalResult => doSomethingExtraNice(optionalResult))
  4. .catch(e => {})) // Ignore if optional stuff fails; proceed.
  5. .then(() => moreCriticalStuff())
  6. .catch(e => console.log("Critical failure: " + e.message));

这里内部的 .catch() 方法只会捕获来自 doSomethingOptional() 和 doSomethingExtraNice() 的错误,而且是在 moreCriticalStuff() 继续执行后;如果 moreCriticalStuff() 失败,其错误只会被外部的 .catch() 方法捕获。

常见错误

应避免常见的 Promise 链错误:


async/await

永远使用 try-catch 块包裹 await 语句!

asycn function 用于定义一个异步函数(通过事件循环异步执行),其会通过一个隐式的 Promise 返回结果:

  1. async function asynCall() {
  2. const resolveAfter2Seconds = (ms) => {setTimeout(() => {console.log('resolve');}, ms); return 'ok';};
  3. console.log('calling');
  4. let result = await resolveAfter2Seconds(2000);
  5. console.log(result);
  6. return 'resolved';
  7. }
  8. asynCall();
  9. //calling
  10. //ok
  11. //Promise {<resolved>: "resloved"}
  12. //'resolve' after 2s for setTimeout

async 函数的返回一定是一个 Promise 对象,且 resolved 的值就是 return 语句的值(没有则 resolve(undefined)),或以 async 抛出的异常来 rejected(返回值被包裹进该 Promise)。

await 操作符用于等待一个 Promise 对象。

await 表达式会暂停 async 函数的执行,等待一个 Promise 的结果;如果后面跟的不是一个 Promise,则将表达式的 value 转化为一个 resolved Promise;如果 Promise 处理异常,则 throw

如果跟的 Promise 成功,则继续执行,await 表达式的 value 为 Promise 的 value。
如果跟的 Promise 失败,抛出 rejected 的 value。

看做通过一个 Promise.then(value => value, err => err) 得到返回值。

只有在 async 函数内部能使用 await。

通过 async 方法重写 Promise 链

  1. function getProcessedData(url) {
  2. return downloadData(url) // returns a promise
  3. // if rejected or throw
  4. .catch(e => {
  5. return downloadFallbackData(url) // returns a promise
  6. })
  7. // finally return
  8. .then(v => {
  9. return processDataInWorker(v); // returns a promise
  10. });
  11. }

可以改写成一个有 try-catch 块的 async 函数:

  1. async function getProcessedData(url) {
  2. let v;
  3. try {
  4. v = await downloadData(url);
  5. } catch(e) {
  6. v = await downloadFallbackData(url);
  7. }
  8. return processDataInWorker(v);
  9. }

return 语句中无 await 声明,因为 async 函数的返回值会被隐式包裹进 Promise.resolve。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注