# Promise

返回:前端基础知识

# 处理错误

为了演示 Promise 处理方式,我们先回到一开始的那个事例:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);
1
2
3
4
5
6
7
8
9

相对简单抛出异常,我们可以使用 Promise.reject 和 Promise.resolve:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }

  const result = string.toUpperCase();

  return Promise.resolve(result);
}
1
2
3
4
5
6
7
8
9

因为使用了 Promise ,所以可以使用 then 来接收返回的内容,或者用 catch 来捕获出现的错误。

toUppercase(99)
  .then((result) => result)
  .catch((error) => console.error(error.message));
1
2
3

除了 then 和 catch , Promise 中还有 finally 方法,这类似于 try/catch 中的 finally。

toUppercase(99)
  .then((result) => result)
  .catch((error) => console.error(error.message))
  .finally(() => console.log("Run baby, run"));
1
2
3
4

# Promise, error, 和 throw

返回顶部

使用 Promise.reject 可以很方便的抛出错误:

Promise.reject(TypeError("Wrong type given, expected a string"));
1

除了 Promise.reject,我们也可以通过抛出异常来退出 Promise。

考虑以下示例:

Promise.resolve("A string").then((value) => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});
1
2
3
4
5

要停止异常传播,我们照常使用 catch:

Promise.resolve("A string")
  .then((value) => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch((reason) => console.log(reason.message));
1
2
3
4
5
6
7

# 使用 Promise 来处理定时器中的异常

返回顶部

解决方案就是使用 Promise:

function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}
1
2
3
4
5
6
7

使用 reject,我们启动了一个 Promise 拒绝,它携带一个错误对象。此时,我们可以使用 catch 处理异常:

failAfterOneSecond().catch((reason) => console.error(reason.message));
1

# 使用 Promise.all 来处理错误

返回顶部

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");

Promise.all([promise1, promise2]).then((results) => console.log(results));

// [ 'All good!', 'All good here too!' ]
1
2
3
4
5
6

如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

Promise.all([promise1, promise2, promise3])
  .then((results) => console.log(results))
  .catch((error) => console.error(error.message));

// No good, sorry!
1
2
3
4
5
6
7
8
9

同样,无论 Promise.all 的结果如何运行函数,finally 都会被执行:

Promise.all([promise1, promise2, promise3])
  .then((results) => console.log(results))
  .catch((error) => console.error(error.message))
  .finally(() => console.log("Always runs!"));
1
2
3
4

# 使用 Promise.any 来处理错误

返回顶部

TIP

Promise.any() (Firefox > 79, Chrome > 85) 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和 Promise.all()是相反的。

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then((result) => console.log(result))
  .catch((error) => console.error(error))
  .finally(() => console.log("Always runs!"));
1
2
3
4
5
6
7

在这里,我们使用 catch 处理错误,输出如下:

AggregateError: No Promise in Promise.any was resolved
Always runs!
1
2

AggregateError 对象具有与基本 Error 相同的属性,外加 errors 属性:

//
  .catch(error => console.error(error.errors))
//
1
2
3

此属性是由 reject 产生的每个单独错误的数组

[Error: "No good, sorry!, Error: "Bad day ..."]
1

# 使用 Promise.race 来处理错误

返回顶部

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then((result) => console.log(result));

// The first!
1
2
3
4
5
6

这里说明,第一个 Promise 比第二个行执行完。那包含拒绝的情况又是怎么样的?

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then((result) =>
  console.log(result)
);

// The first!
1
2
3
4
5
6
7
8
9

如果把 reject 放在第一个又会怎么样?

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then((result) => console.log(result))
  .catch((error) => console.error(error.message));

// Ouch!
1
2
3
4
5
6
7
8
9

# 使用 Promise.allSettled 来处理错误

返回顶部

Promise.allSettled()方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then((results) => console.log(results))
  .catch((error) => console.error(error))
  .finally(() => console.log("Always runs!"));
1
2
3
4
5
6
7

我们传递给 Promise.allSettled 一个由两个 Promise 组成的数组:一个已解决,另一个被拒绝。

这种情况 catch 不会被执行, finally 永远会执行。

[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]
1
2
3
4
5
6
7

# 使用 async/await 来处理错误

返回顶部

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}
1
2
3
4
5
6
7

只要在函数前面加上 async,该函数就会返回一个 Promise。这意味着我们可以在函数调用之后进行 then、catch 和 finally 操作

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase("abc")
  .then((result) => console.log(result))
  .catch((error) => console.error(error.message))
  .finally(() => console.log("Always runs!"));
1
2
3
4
5
6
7
8
9
10
11
12

当从 async 函数抛出异常时,我们就可以使用 catch 来捕获。

最重要的是,除了这种方式外,我们可以还使用 try/catch/finally,就像我们使用同步函数所做的一样。

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

输出:

Wrong type given, expected a string
Always runs!
1
2

# 使用 async generators 来处理错误

返回顶部

JavaScript 中的 async generators 是能够生成 Promises 而不是简单值的生成器函数。

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}
1
2
3
4
5

基于 Promise,此处适用于错误处理的相同规则。在异步生成器中 throw 将会触发 Promise 的 reject,我们可以使用 catch 对其进行拦截。

为了使用异步生成器的 Promise,我们可以这样做:

  • then 方法
  • 异步遍历

从上面我们知道,在两次调用 yield 之后,下一次会抛出一个异常:

const go = asyncGenerator();

go.next().then((value) => console.log(value));
go.next().then((value) => console.log(value));
go.next().catch((reason) => console.error(reason.message));
1
2
3
4
5

输出结果:

{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!
1
2
3

别一种是使用 异步遍历与 for await...of:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();
1
2
3
4
5
6
7
8
9
10
11
12
13

有了 async/await 我们可以使用 try/catch 来捕获异常:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

输出结果:

33
99
Something went wrong!
1
2
3

从异步生成器函数返回的迭代器对象也具有 throw()方法,非常类似于其同步副本。在此处的迭代器对象上调用 throw()不会引发异常,但是会被 Promise 拒绝

async function* asyncGenerator() {
  yield 33;
  yield 99;
  yield 11;
}

const go = asyncGenerator();

go.next().then((value) => console.log(value));
go.next().then((value) => console.log(value));

go.throw(Error("Let's reject!"));

go.next().then((value) => console.log(value)); // value is undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14

要从外部处理这种情况,我们可以做:

go.throw(Error("Let's reject!")).catch((reason) =>
  console.error(reason.message)
);
1
2
3

# Promise.any

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise

const promise1 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{
  console.log(values);
})
.catch(e=>{
  console.log(e);
});
1
2
3
4
5
6
7
8
9
10

# Promise.allSettled

返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise4 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1,promise2,promise3, promise4]
Promise.allSettled(promiseList)
.then(values=>{
  console.log(values)
});
1
2
3
4
5
6
7
8
9

# Promise.finally

一个 Promise 调用链要么成功到达最后一个.then(),要么失败触发.catch()。在某些情况下,你想要在无论 Promise 运行成功还是失败,运行相同的代码,例如清除,删除对话,关闭数据库连接等。

  • .finally()允许你指定最终的逻辑
function doSomething() {
  doSomething1()
    .then(doSomething2)
    .then(doSomething3)
    .catch(err => {
      console.log(err);
    })
    .finally(() => {
      // finish here!
    });
}
1
2
3
4
5
6
7
8
9
10
11

# 异步逻辑包装在 promise

可以使用 promise,将异步逻辑包装在 promise 中,成功时 resolve 或在失败时 reject 使用“then”来处理成功的情况,使用 catch 来处理异常。

const myPromise = new Promise(function(res, rej) {
  setTimeout(function() {
    if (Math.random() < 0.9) {
      return res("Hooray!");
    }
    return rej("Oh no!");
  }, 1000);
});
myPromise
  .then(function(data) {
    console.log("Success: " + data);
  })
  .catch(function(err) {
    console.log("Error: " + err);
  });
// 如果 Math.random() 返回的值小于 0.9,则打印以下内容
// "Success: Hooray!"
// 相反,则打印以下内容
// "Error: On no!"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Async Await

back

掌握了 promise 的用法后,你可能也会喜欢 async await,它只是一种基于 promise 的“语法糖”。在下面的示例中,咱们创建了一个 async 函数,并 await greeter。

const greeter = new Promise((res, rej) => {
  setTimeout(() => res("Hello world!"), 2000);
});
async function myFunc() {
  const greeting = await greeter;
  console.log(greeting);
}
myFunc();
// 'Hello world!'
1
2
3
4
5
6
7
8
9