11 期约与异步函数

ECMAScript 6 及之后的几个版本逐步加大了对异步编程机制的支持,Promise(期约)是新增的引用类型,支持优雅地定义和组织异步逻辑。接下来几个版本增加了使用 async 和 await 关键字定义异步函数的机制。

注意,本章示例将大量使用异步日志输出的方式 setTimeout(console.log, 0, … params),旨在演示执行顺序及其他异步行为。异步输出的内容看起来虽然像是同步输出的,但实际上是异步打印的。这样可以让期约等返回的值达到其最终状态。
此外,浏览器控制台的输出经常能打印出 JavaScript 运行中无法获取的对象信息(比如期约的状态)。这个特性在示例中广泛使用,以便辅助读者理解相关概念。

1 INTRODUCTION TO ASYNCHRONOUS PROGRAMMING

The duality between synchronous and asynchronous behavior is a fundamental concept in computer science—especially in a single-threaded event loop model such as JavaScript. Asynchronous behavior is borne out of the need to optimize for higher computational throughput in the face of high-latency operations. If it is feasible to run other instructions while a computation is completing and still maintain a stable system, then it is pragmatic to do so.

Importantly, an asynchronous operation is not necessarily a computationally intensive or high-latency operation. It can be used anywhere it doesn’t make sense to block a thread of execution to wait for the asynchronous behavior to occur.

1.1 Synchronous vs. Asynchronous JavaScript

Synchronous behavior is analogous to sequential processor instructions in memory. Each instruction is executed strictly in the order in which it appears, and each is also capable of immediately retrieving information that is stored locally within the system (for example: in a processor register or in system memory). As a result, it is easy to reason about the program state (for example, the value of a variable) at any given point in code.

A trivial example of this would be performing a simple arithmetic operation:

let x = 3; 
x = x + 4; 

At each step in this program, it is possible to reason about the state of the program because execution will not proceed until the previous instruction is completed. When the last instruction completes, the computed value of x is immediately available for use.

This JavaScript snippet is easy to reason about because it is not difficult to anticipate what low-level instructions this will be compiled to (from JavaScript to x86, for example). Presumably, the operating system will allocate some memory for a floating point number on the stack, perform an arithmetic operation on that value, and write the result to that allocated memory. All of these instructions exist serially inside a single thread of execution. At each point in the compiled low-level program, you are
well-equipped to assert what can and cannot be known about the state of the system.

Conversely, asynchronous behavior is analogous to interrupts, where an entity external to the current process is able to trigger code execution. An asynchronous operation is often required because it is infeasible to force the process to wait a long time for an operation to complete (which is the case with a synchronous operation). This long wait might occur because the code is accessing a high-latency resource, such as sending a request to a remote server and awaiting a response.

A trivial JavaScript example of this would be performing an arithmetic operation inside a timeout:

let x = 3; 
setTimeout(() => x = x + 4, 1000); 

This program eventually performs the same work as the synchronous one—adding two numbers together—but this thread of execution cannot know exactly when the value of x will change because that depends on when the callback is dequeued from the message queue and executed.

This code is not as easy to reason about. Although the low-level instructions used in this example ultimately do the same work as the previous example, the second chunk of instructions (the addition operation and assignment) are triggered by a system timer, which will generate an interrupt to enqueue execution. Precisely when this interrupt will be triggered is a black box to the JavaScript runtime, so it effectively cannot be known exactly when the interrupt will occur (although it is guaranteed to be after the current thread of synchronous execution completes, since the callback will not yet have had an opportunity to be dequeued and executed). Nevertheless, you are generally unable to assert when the system state will change after the callback is scheduled.

For the value of x to become useful, this asynchronously executed function would need to signal to the rest of the program that it has updated the value of x. However, if the program does not need this value, then it is free to proceed and do other work instead of waiting for the result.

Designing a system to know when the value of x can be read is surprisingly tricky. Implementations of such a system within the JavaScript have undergone several iterations.

1.2 Legacy Asynchronous Programming Patterns

待补充 348

2 PROMISES

空缺的内容都没用。

2.2 Promise Basics

As of ECMAScript 6, Promise is a supported reference type and can be instantiated with the new operator. Doing so requires passing an executor function parameter (covered in an upcoming section), which here is an empty function object to please the interpreter:

let p = new Promise(() => {}); 
setTimeout(console.log, 0, p); // Promise <pending> 

chrome中执行结果如下:
在这里插入图片描述

If an executor function is not provided, a SyntaxError will be thrown.

2.2.1 The Promise State Machine

When passing a promise instance to console.log, the console output (which may vary between browsers) indicates that this promise instance is pending. As mentioned previously, a promise is a stateful object that can exist in one of three states:

  • Pending
  • Fulfilled (sometimes also referred to as resolved)
  • Rejected

A pending state is the initial state a promise begins in. From a pending state, a promise can become settled by transitioning to a fulfilled state to indicate success, or a rejected state to indicate failure. This transition to a settled state is irreversible; once a transition to either fulfilled or rejected occurs, the state of the promise can never change. Furthermore, it is not guaranteed that a promise will ever leave the pending state. Therefore, well-structured code should behave properly if the promise successfully resolves, if the promise rejects, or if it never exits the pending state

Importantly, the state of a promise is private and cannot be directly inspected in JavaScript. The reason for this is primarily to prevent synchronous programmatic handling of a promise object based on its state when it is read. Furthermore, the state of a promise cannot be mutated by external JavaScript. This is for the same reason the state cannot be read: The promise intentionally encapsulates a block of asynchronous behavior, and external code performing synchronous definition of its
state would be antithetical to its purpose.

2.2.2 Resolved Values, Rejection Reasons, and Utility of Promises

There are two primary reasons the Promise construct is useful. The first is to abstractly represent a block of asynchronous execution. The state of the promise is indicative of whether or not the promise has yet to complete execution. The pending state indicates that execution has not yet begun or is still in progress. The fulfilled state is a nonspecific indicator that the execution has completed successfully. The rejected state is a nonspecific indicator that the execution did not complete successfully.

In some cases, the internal state machine is all the utility a promise needs to provide: the mere knowledge that a piece of asynchronous code has completed is sufficient for informing program flow. For example, suppose a promise is dispatching an HTTP request to a server. The request returning with a status of 200–299 might be sufficient to transition the promise state to fulfilled. Similarly, the request returning with a status that is not 200-299 would transition the promise state
to rejected.

In other cases, the asynchronous execution that the promise is wrapping is actually generating a value, and the program flow will expect this value to be available when the promise changes state. Alternately, if the promise rejects, the program flow will expect the reason for rejection when the promise changes state. For example, suppose a promise is dispatching an HTTP request to a server and expecting it to return JSON. The request returning with a status of 200–299 might be sufficient
to transition the promise to fulfilled, and the JSON string will be available inside the promise. Similarly, the request returning with a status that is not 200–299 would transition the promise state to rejected, and the reason for rejection might be an Error object containing the text accompanying the HTTP status code.

To support these two use cases, every promise that transitions to a fulfilled state has a private internal value. Similarly, every promise that transitions to a rejected state has a private internal reason. Both value and reason are an immutable reference to a primitive or object. Both are optional and will default to undefined. Asynchronous code that is scheduled to execute after a promise reaches a certain settled state is always provided with the value or reason

2.2.3 Controlling Promise State with the Executor

Because the state of a promise is private, it can only be manipulated internally. This internal manipulation is performed inside the promise’s executor function. The executor function has two primary duties: initializing the asynchronous behavior of the promise, and controlling any eventual state transition. Control of the state transition is accomplished by invoking one of its two function parameters, typically named resolve and reject. Invoking resolve will change the state to fulfilled; invoking reject will change the state to rejected. Invoking rejected() will also throw an error (this error behavior is covered more later).

let p1 = new Promise((resolve, reject) => resolve()); 
setTimeout(console.log, 0, p1); // Promise <resolved> 

let p2 = new Promise((resolve, reject) => reject()); 
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)

In the preceding example, there isn’t really any asynchronous behavior occurring because the state of each promise is already changed by the time the executor function exits. Importantly, the executor function will execute synchronously, as it acts as the initializer for the promise. This order of execution is demonstrated here:

new Promise(() => setTimeout(console.log, 0, 'executor')); 
setTimeout(console.log, 0, 'promise initialized'); 

// executor 
// promise initialized

You can delay the state transition by adding a setTimeout:

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));

// When this console.log executes, the timeout callback has not yet executed:
setTimeout(console.log, 0, p); // Promise <pending> 

Once either resolve or reject is invoked, the state transition cannot be undone. Attempts to further mutate the state will silently be ignored. This is demonstrated here:

let p = new Promise((resolve, reject) => { 
 resolve();
 reject(); 	// No effect
}); 

setTimeout(console.log, 0, p); // Promise <resolved>

You can avoid promises getting stuck in a pending state by adding timed exit behavior. For example, you can set a timeout to reject the promise after 10 seconds:

let p = new Promise((resolve, reject) => { 
 	setTimeout(reject, 10000); // After 10 seconds, invoke reject()
 	// Do executor things
}); 

setTimeout(console.log, 0, p); 		// Promise <pending> 
setTimeout(console.log, 11000, p); 	// Check state after 11 seconds
// (After 10 seconds) Uncaught error 
// (After 11 seconds) Promise <rejected> 

Because a promise can only change state a single time, this timeout behavior allows you to safely set a maximum on the amount of time a promise can remain in the pending state. If the code inside the executor were to resolve or reject prior to the timeout, the timeout handler’s attempt to reject the promise will be silently ignored.

2.2.4 Promise Casting with Promise.resolve()

A promise does not necessarily need to begin in a pending state and utilize an executor function to reach a settled state. It is possible to instantiate a promise in the “resolved” state by invoking the Promise.resolve() static method. The following two promise instantiations are effectively equivalent:

let p1 = new Promise((resolve, reject) => resolve()); 
let p2 = Promise.resolve(); 

The value of this resolved promise will become the first argument passed to Promise.resolve(). This effectively allows you to “cast” any value into a promise:

setTimeout(console.log, 0, Promise.resolve()); 
// Promise <resolved>: undefined 

setTimeout(console.log, 0, Promise.resolve(3)); 
// Promise <resolved>: 3 

// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6)); 
// Promise <resolved>: 4 

Perhaps the most important aspect of this static method is its ability to act as a passthrough when the argument is already a promise. As a result, Promise.resolve() is an idempotent method, as demonstrated here:

let p = Promise.resolve(7); 

setTimeout(console.log, 0, p === Promise.resolve(p)); 
// true 

setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p))); 
// true 

This idempotence will respect the state of the promise passed to it:

let p = new Promise(() => {}); 

setTimeout(console.log, 0, p); // Promise <pending> 
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending> 

setTimeout(console.log, 0, p === Promise.resolve(p)); // true

Beware that this static method will happily wrap any non-promise, including an error object, as a resolved promise, which might lead to unintended behavior:

let p = Promise.resolve(new Error('foo')); 

setTimeout(console.log, 0, p); 
// Promise <resolved>: Error: foo 
2.2.5 Promise.reject()

与 Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)。下面的两个期约实例实际上是一样的:

let p1 = new Promise((resolve, reject) => reject()); 
let p2 = Promise.reject(); 

这个拒绝的期约的理由就是传给 Promise.reject()的第一个参数。这个参数也会传给后续的拒绝处理程序:

let p = Promise.reject(3); 
setTimeout(console.log, 0, p); // Promise <rejected>: 3 

p.then(null, (e) => setTimeout(console.log, 0, e)); // 3

关键在于,Promise.reject()并没有照搬 Promise.resolve()的幂等逻辑。如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:

setTimeout(console.log, 0, Promise.reject(Promise.resolve())); 
// Promise <rejected>: Promise <resolved> 
2.2.6 同步/异步执行的二元性

同步/异步执行的二元性了这一点,其中包含了两种模式下抛出错误的情形:

try { 
 	throw new Error('foo'); 
} catch(e) { 
 	console.log(e); // Error: foo 
} 

try { 
 	Promise.reject(new Error('bar')); 
} catch(e) { 
 	console.log(e); 
} 
// Uncaught (in promise) Error: bar 

第一个 try/catch 抛出并捕获了错误,第二个 try/catch 抛出错误却没有捕获到。乍一看这可能有点违反直觉,因为代码中确实是同步创建了一个拒绝的期约实例,而这个实例也抛出了包含拒绝理由的错误。这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式的媒介。

在前面的例子中,拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。因此,try/catch 块并不能捕获该错误。代码一旦开始以异步模式执行,则唯一与之交互的方式就是使用异步结构——更具体地说,就是期约的方法。

2.3 Promise Instance Methods

The methods exposed on a promise instance serve to bridge the gap between the synchronous external code path and the asynchronous internal code path. These methods can be used to access data returned from an asynchronous operation, handle success and failure outcomes of the promise, serially evaluate promises, or add functions that only execute once the promise enters a terminal state.

2.3.1 Implementing the Thenable Interface

For the purposes of ECMAScript asynchronous constructs, any object that exposes a then() method is considered to implement the Thenable interface. The following is an example of the simplest possible class that implements this interface:

class MyThenable { 
 	then() {} 
} 

The ECMAScript Promise type implements the Thenable interface. This simplistic interface is not to be confused with other interfaces or type definitions in packages like TypeScript, which lay out a much more specific form of a Thenable interface.

2.3.2 Promise.prototype.then()

The method Promise.prototype.then() is the primary method that is used to attach handlers to a promise instance. The then() method accepts up to two arguments: an optional onResolved handler function, and an optional onRejected handler function. Each will execute only when the promise upon which they are defined reaches its respective “fulfilled” or “rejected” state.

function onResolved(id) { 
 	setTimeout(console.log, 0, id, 'resolved'); 
} 

function onRejected(id) { 
 	setTimeout(console.log, 0, id, 'rejected'); 
} 

let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000)); 
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
 
p1.then(() => onResolved('p1'), 
 		() => onRejected('p1')); 
p2.then(() => onResolved('p2'), 
 		() => onRejected('p2')); 
//(3 秒后)
// p1 resolved 
// p2 rejected 

Because a promise can only transition to a final state a single time, you are guaranteed that execution of these handlers is mutually exclusive.

As described earlier, both handler arguments are completely optional. Any non-function type provided as an argument to then() will be silently ignored. If you wish to explicitly provide only an onRejected handler, providing undefined as the onResolved argument is the canonical choice. This allows you to avoid creating a temporary object in memory just to be ignored by the interpreter, and it will also please type systems that expect an optional function object as an argument.

function onResolved(id) { 
 	setTimeout(console.log, 0, id, 'resolved'); 
} 

function onRejected(id) { 
 	setTimeout(console.log, 0, id, 'rejected'); 
} 

let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000)); 
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000)); 

// Non-function handlers are silently ignored, not recommended
p1.then('gobbeltygook');
 
// Canonical form of explicit onResolved handler skipping
p2.then(null, () => onRejected('p2')); 

// p2 rejected(3 秒后)

The Promise.prototype.then() method returns a new promise instance:

let p1 = new Promise(() => {}); 
let p2 = p1.then(); 
setTimeout(console.log, 0, p1); // Promise <pending> 
setTimeout(console.log, 0, p2); // Promise <pending> 
setTimeout(console.log, 0, p1 === p2); // false 

This new promise instance is derived from the return value of the onResolved handler. The return value of the handler is wrapped in Promise.resolve() to generate a new promise. If no handler function is provided, the method acts as a passthrough for the initial promise’s resolved value. If there is no explicit return statement, the default return value is undefined and wrapped in a Promise.resolve().

let p1 = Promise.resolve('foo'); 

// Calling then() with no handler function acts as a passthrough
let p2 = p1.then(); 
setTimeout(console.log, 0, p2); // Promise <resolved>: foo 

// These are equivalent
let p3 = p1.then(() => undefined); 
let p4 = p1.then(() => {}); 
let p5 = p1.then(() => Promise.resolve()); 

setTimeout(console.log, 0, p3); // Promise <resolved>: undefined 
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined 
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined

Explicit return values are wrapped in Promise.resolve():

... 
// These are equivalent:
let p6 = p1.then(() => 'bar'); 
let p7 = p1.then(() => Promise.resolve('bar')); 

setTimeout(console.log, 0, p6); // Promise <resolved>: bar 
setTimeout(console.log, 0, p7); // Promise <resolved>: bar 

// Promise.resolve() preserves the returned promise
let p8 = p1.then(() => new Promise(() => {})); 
let p9 = p1.then(() => Promise.reject()); 
// Uncaught (in promise): undefined 

setTimeout(console.log, 0, p8); // Promise <pending> 
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined 

Throwing an exception will return a rejected promise:

... 

let p10 = p1.then(() => { throw 'baz'; }); 
// Uncaught (in promise) baz 

setTimeout(console.log, 0, p10); // Promise <rejected> baz 

Importantly, returning an error will not trigger the same rejection behavior, and will instead wrap the error object in a resolved promise:

... 

let p11 = p1.then(() => Error('qux')); 

setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux

The onRejected handler behaves in the same way: values returned from the onRejected handler are wrapped in Promise.resolve(). This might seem counterintuitive at first, but the onRejected handler is doing its job to catch an asynchronous error. Therefore, this rejection handler completing execution without throwing an additional error should be considered expected promise behavior and therefore return a resolved promise.

The following code snippet is the Promise.reject() analog of the previous examples using Promise.resolve():

let p1 = Promise.reject('foo'); 

// Calling then() with no handler function acts as a passthrough
let p2 = p1.then(); 
// Uncaught (in promise) foo
setTimeout(console.log, 0, p2); // Promise <rejected>: foo 

// These are equivalent
let p3 = p1.then(null, () => undefined); 
let p4 = p1.then(null, () => {}); 
let p5 = p1.then(null, () => Promise.resolve()); 

setTimeout(console.log, 0, p3); // Promise <resolved>: undefined 
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined 
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined 

// These are equivalent
let p6 = p1.then(null, () => 'bar'); 
let p7 = p1.then(null, () => Promise.resolve('bar')); 

setTimeout(console.log, 0, p6); // Promise <resolved>: bar 
setTimeout(console.log, 0, p7); // Promise <resolved>: bar 

// Promise.resolve() preserves the returned promise
let p8 = p1.then(null, () => new Promise(() => {})); 
let p9 = p1.then(null, () => Promise.reject()); 
// Uncaught (in promise): undefined 

setTimeout(console.log, 0, p8); // Promise <pending> 
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined 

let p10 = p1.then(null, () => { throw 'baz'; }); 
// Uncaught (in promise) baz 

setTimeout(console.log, 0, p10); // Promise <rejected>: baz 

let p11 = p1.then(null, () => Error('qux')); 

setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux
2.3.3 Promise.prototype.catch()

The Promise.prototype.catch() method can be used to attach only a reject handler to a promise. It only takes a single argument, the onRejected handler function. The method is no more than syntactical sugar, and is no different than using Promise.prototype.then(null, onRejected).

The following code demonstrates this equivalence:

let p = Promise.reject();
let onRejected = function(e) {
 	setTimeout(console.log, 0, 'rejected');
};

// These two reject handlers behave identically:
p.then(null, onRejected); // rejected
p.catch(onRejected); // rejected

The Promise.prototype.catch() method returns a new promise instance:

let p1 = new Promise(() => {});
let p2 = p1.catch();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false

With respect to creation of the new promise instance, Promise.prototype.catch() behaves identically to the onRejected handler of Promise.prototype.then().

3 异步函数

异步函数,也称为“async/await”(语法关键字),是 ES6 期约模式在 ECMAScript 函数中的应用。async/await 是 ES8 规范新增的。这个特性从行为和语法上都增强了 JavaScript,让以同步方式写的代码能够异步执行。下面是一个最简单的例子,这个期约在超时之后会解决为一个值:

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); 

这个期约在 1000 毫秒之后解决为数值 3。如果程序中的其他代码要在这个值可用时访问它,则需要写一个解决处理程序:

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); 

p.then((x) => console.log(x)); // 3 

这其实是很不方便的,因为其他代码都必须塞到期约处理程序中。不过可以把处理程序定义为一个函数:

function handler(x) { console.log(x); } 

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); 

p.then(handler); // 3

这个改进其实也不大。这是因为任何需要访问这个期约所产生值的代码,都需要以处理程序的形式来接收这个值。也就是说,代码照样还是要放到处理程序里。ES8 为此提供了 async/await 关键字。

3.1 异步函数

ES8 的 async/await 旨在解决利用异步结构组织代码的问题。为此,ECMAScript 对函数进行了扩展,为其增加了两个新关键字:async 和 await。

3.1.1 async

async 关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上:

async function foo() {} 

let bar = async function() {}; 

let baz = async () => {}; 

class Qux { 
 	async qux() {} 
} 

使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。正如下面的例子所示,foo()函数仍然会在后面的指令之前被求值:

async function foo() { 
 	console.log(1); 
} 

foo(); 
console.log(2); 

// 1 
// 2 

不过,异步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。在函数外部调用这个函数可以得到它返回的期约:

async function foo() { 
 	console.log(1); 
 	return 3; 
} 

// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2);

// 1 
// 2 
// 3 

当然,直接返回一个期约对象也是一样的:

async function foo() { 
 	console.log(1); 
 	return Promise.resolve(3); 
} 

// 给返回的期约添加一个解决处理程序
foo().then(console.log); 
console.log(2);
 
// 1 
// 2 
// 3 

异步函数的返回值期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果返回的是实现 thenable 接口的对象,则这个对象可以由提供给 then()的处理程序“解包”。如果不是,则返回值就被当作已经解决的期约。下面的代码演示了这些情况:

// 返回一个原始值 
async function foo() { 
	return 'foo'; 
} 
foo().then(console.log); 
// foo

// 返回一个没有实现 thenable 接口的对象
async function bar() { 
	return ['bar']; 
} 
bar().then(console.log); 
// ['bar']

// 返回一个实现了 thenable 接口的非期约对象
async function baz() { 
 	const thenable = { 
 		then(callback) { callback('baz'); } 
 	}; 
 	return thenable; 
} 
baz().then(console.log); 
// baz

// 返回一个期约
async function qux() { 
 return Promise.resolve('qux'); 
} 
qux().then(console.log); 
// qux

与在期约处理程序中一样,在异步函数中抛出错误会返回拒绝的期约:

async function foo() { 
	console.log(1); 
 	throw 3; 
} 

// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
 
// 1 
// 2 
// 3

不过,拒绝期约的错误不会被异步函数捕获:

async function foo() { 
 	console.log(1); 
 	Promise.reject(3); 
} 

// Attach a rejected handler to the returned promise 
foo().catch(console.log); 
console.log(2); 

// 1 
// 2 
// Uncaught (in promise): 3 

待补充 375

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值