JavaScript 异步(Promise + async/await)
一、Promise
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值
一个 Promise 对象代表一个在这个 Promise 被创建出来时不一定已知的值。能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 Promise,以便在未来某个时候把值交给使用者。
一个 Promise 必然处于以下几种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,用 Promise 的 then 方法排列起来的相关处理程序就会被调用。
(如果一个 Promise 已经被兑现(fulfilled)或被拒绝(rejected),那么也可以说它处于已敲定(settled)状态。还有一个常和 Promise 一起使用的术语:已决议(resolved),它表示 Promise 已经处于已敲定(settled)状态,或者为了匹配另一个 Promise 的状态被“锁定”了。)
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 Promise,所以他们可以被链式调用。即如果 then() 里没有返回值或返回值不是 Promise,链式调用的下一个 then((res) => {}) 的 result 为 undefined,并且如果这个 then 里 如果有异步代码,这个 then 一定要返回,否则会出现下一个 then 先执行,而上一个 then 里的 then 未执行的情况。

Promise.reject(reason): 返回一个状态为 rejected 的 Promise 对象,并将给定的失败信息传递给对应的处理方法
Promise.resolve(value): 返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable(即带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法决定;否则(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled,并且将该 value 传递给对应的 then 方法。
Promise 是宏任务,和 settimeout 相同,但是 .then() 是微任务
const promise = new Promise(function (resolve, reject) {
console.log("Promise callback");
resolve();
}).then(function (result) {
console.log("Promise callback (.then)");
});
setTimeout(function () {
console.log("event-loop cycle: Promise (fulfilled)", promise);
}, 0);
console.log("Promise (pending)", promise);
catch 无法捕获更高层的错误
doSomethingCritical()
.then((result) =>
doSomethingOptional(result)
.then((optionalResult) => doSomethingExtraNice(optionalResult))
// catch 只捕获 doSomethingOptional 和 doSomethingExtraNice,doSomethingCritical 由最下面的 catch 捕获
.catch((e) => {})
) // Ignore if optional stuff fails; proceed.
.then(() => moreCriticalStuff())
.catch((e) => console.error("Critical failure: " + e.message));
Promise 静态方法
一、Promise.all()
The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throw an error, and will reject with this first rejection message / error, whether or not the other promise have settled.
接收多个 promise(注意,不一定是promise数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。),输出为一个promise,这个promise的 resolve(如果promise的状态都能转换为 fulfill)是输入的promise数组的resolve数组。
// Using Promise.all
const p1 = Promise.resolve(3);
const p2 = 1337;
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
tips:
1、If the iterable contains non-promises values, they will be ignored, but still counted in the returned promise array value (if the promise is fulfilled).
2、Promise.all() is asynchronicity, or synchronicity (only if the iterable passed is empty). The same thing happens if Promise.all rejects.
const p = Promise.all([]); // synchronously, will be immediately resolved
const p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
console.log(p);
console.log(p2)
setTimeout(function() {
console.log('the stack is now empty');
console.log(p2);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[0] }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
3、Promise.all is rejected if any of the element are rejected.
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('one'), 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('two'), 2000);
});
const p3 = new Promise((resolve, reject) => {
reject(new Error('reject'));
});
// Using .catch:
Promise.all([p1, p2, p3])
.then(values => {
console.log(values);
})
.catch(error => {
// p3 rejected immediately, then Promise.all will reject immediately.
console.error(error.message)
});
//From console:
//"reject"
It is possible to change this behavior by handing possible rejections.
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
下面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1_delayed_resolution'), 1000);
});
const p2 = new Promise((resolve, reject) => {
reject(new Error('p2_immediate_rejection'));
});
Promise.all([
p1.catch(error => { return error }),
p2.catch(error => { return error }),
]).then(values => {
console.log(values[0]) // "p1_delayed_resolution"
console.error(values[1]) // "Error: p2_immediate_rejection"
}).catch(e => console.log(e))
二、Promise.allSettled()
The promise.allSettled() method returns a promise that fulfills(该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected) after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
最常见被用于:希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作或者想知道每一个 promise的结果。
In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other / if you'd like to immediately reject upon any of them rejecting.
Promise.allSettled([
Promise.resolve(33),
new Promise(resolve => setTimeout(() => resolve(66), 0)),
99,
Promise.reject(new Error('an error'))
])
.then(values => console.log(values));
// [
// {status: "fulfilled", value: 33},
// {status: "fulfilled", value: 66},
// {status: "fulfilled", value: 99},
// {status: "rejected", reason: Error: an error}
// ]
const values = await Promise.allSettled([
Promise.resolve(33),
new Promise(resolve => setTimeout(() => resolve(66), 0)),
99,
Promise.reject(new Error('an error'))
])
console.log(values)
// [
// {status: "fulfilled", value: 33},
// {status: "fulfilled", value: 66},
// {status: "fulfilled", value: 99},
// {status: "rejected", reason: Error: an error}
// ]
三、Promise.any()
Promise.any() takes an iterable of Promise objects. It returns a single promise that fulfills as soon as any of the promises in the iterable fulfills, with the value of the fulfilled promise. If no promises in the iterable fulfill (if all of the given promises are rejected), then the returns promise is rejected with an AggregateError, a new subclass of Error that groups together individual errors.
This method rejects upon receiving an empty iterable, since, truthfully, the iterable contains no items that fulfill. If an empty iterable is passed, then the promise returned by this method is rejected synchronously. The rejected reason is an AggregateError object whose errors property is an empty array.
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2, promise3];
Promise.any(promises).then((value) => console.log(value)); // expected output: "quick"
四、Promise.race()
The Promise.race() method returns a promise that fulfills or rejected as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
即只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);
// 如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
If the iterable passed is empty, the promise returned will be forever pending.
If the iterable contains one or more non-promise value and/or an already settled promise, then Promise.race will settle to the first of these values found in the iterable.
// comparison with Promise.any()
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log('succeeded with value:', value);
}).catch((reason) => {
// Only promise1 is fulfilled, but promise2 is faster
console.log('failed with reason:', reason); // expected output: "failed with reason: two"
});
Promise.any([promise1, promise2]).then((value) => {
// Only promise1 is fulfilled, even though promise2 settled sooner
console.log('succeeded with value:', value); // expected output: "succeeded with value: one"
}).catch((reason) => {
console.log('failed with reason:', reason);
});
tips:
Promise.any() 和 Promise.race() 只会执行满足各自条件最快的 Promise里的内容,其他 Promise 不会执行。
而 Promise.allSettled() 会执行所有的 Promises,并把 Promise 后的结果作为数组返回。
Promise.all() 一旦有一个 Promise reject,就会立刻返回,不会执行之后的 Promises。
五、Promise.reject()
The Promise.reject() method returns a Promise object that is rejected with a given reason.
Promise.reject(reason)方法会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等价于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
Promise.reject()方法的参数,会原封不动地作为reject的 reason,变成后续方法的参数。
The static Promise.reject function returns a Promise that is rejected. For debugging purposes and selective error catching, it is useful to make reason an instanceof Error.
function resolved(result) { console.log('Resolved'); }
function rejected(result) { console.error(result); }
Promise.reject(new Error('fail')).then(resolved, rejected); // expected output: Error: fail
六、Promise.resolve()
The Promise.resolve() method "resolves" a given value to a Promise. If the value is a promise, that promises is returned, it may be either fulfilled or rejected; if the value is a thenable, Promise.resolve() will call the then() method with two callbacks it prepared; otherwise the returned promise will be fulfilled with the value.
将现有对象转为 promise对象
Promise.resolve('foo')
// 等价于
new Promise((resolve, reject) => resolve('foo'))
分为四种情况:
- 参数是一个 Promise 实例:如果参数是 Promise 实例,那么
Promise.resolve将不做任何修改、原封不动地返回这个实例。 -
参数是一个
thenable对象:thenable对象指的是具有then方法的对象,比如下面这个对象。let thenable = { then: function(resolve, reject) { resolve(42); } }; // Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。 let p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 }); - 参数不是具有
then()方法的对象,或根本就不是对象:如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。const p = Promise.resolve('Hello'); p.then(function (s) { console.log(s) }); // Hello // 上面代码生成一个新的 Promise 对象的实例p。 // 由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法), // 返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve()方法的参数,会同时传给回调函数。 -
不带有任何参数:
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。setTimeout(function () { console.log('three'); }, 0); // 需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。 Promise.resolve().then(function () { // 直接返回一个resolved状态的 Promise 对象 console.log('two'); }); console.log('one'); // one // two // three
// Resolve an array
const p = Promise.resolve([1,2,3]);
p.then((v) => {
console.log(v[0]); // 1
});
// Resolve another Promise
const original = Promise.resolve(33);
const cast = Promise.resolve(original);
cast.then((value) => { console.log('value: ' + value); }); // value: 33
console.log('original === cast ? ' + (original === cast)); // original === cast ? true, 即如果Promise.resolve()接收的是Promise,那么会原封不动地返回这个promise。
// Resolving a thenable object
const p1 = Promise.resolve({
then(onFulfill, onReject) {
onFulfill('fulfilled!');
},
});
console.log(p1 instanceof Promise) // true, object casted to a Promise
p1.then(
(v) => {
console.log(v); // "fulfilled!"
},
(e) => {
// not called
},
);
// Thenable throws before callback
// Promise rejects
const thenable = {
then(onFulfilled) {
throw new TypeError('Throwing');
onFulfilled('Resolving');
},
};
const p2 = Promise.resolve(thenable);
p2.then(
(v) => {
// not called
},
(e) => {
console.error(e); // TypeError: Throwing
},
);
// Thenable throws after callback
// Promise resolves
const thenable = {
then(onFulfilled) {
onFulfilled('Resolving');
throw new TypeError('Throwing');
},
};
const p3 = Promise.resolve(thenable);
p3.then(
(v) => {
console.log(v); // "Resolving"
},
(e) => {
// not called
},
);
// Don't call Promise.resolve() on a thenable that resolves to itself. That leads to infinite recursion, because it
// attempts to flatten an infinitely-nested promise
const thenable = {
then(onFulfilled, onRejected) {
onFulfilled(thenable);
},
};
Promise.resolve(thenable) // Will lead to infinite recursion.
Promise
ES6规定,Promise对象是一个构造函数,用来生成 Promise实例。
Promise构造函数接收一个函数 executor作为参数,该函数的两个参数分别是 resolve和reject。它们是两个函数,由JavaScript引擎提供,无需自己部署。
Promise构造函数返回值:Promise构造函数返回一个 promise对象。
executor 函数:被 Promise构造函数执行的函数。接收两个函数作为参数,resolve 和 reject。executor 函数内抛出的任何 errors都会导致该 promise实例成为 rejected状态。executor 函数的返回值会被忽略。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
new Promise(executor)
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue) // fulfilled
// or
// reject("failure reason") // rejected
});
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.onload = () => resolve(xhr.responseText)
xhr.onerror = () => reject(xhr.statusText)
xhr.send()
});
}
即实例化 Promise:提供了异步操作的执行位置,并且可以根据异步操作的执行结果触发 resolve() 或 reject() 函数,改变 promise状态。一旦触发了 resolve() 或 reject() ,如果有链式调用,就会触发 then() 和 catch() 函数的对应操作。
如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
Promise 实例方法
一、Promise.prototype.then()
The then() method returns a Promise. It takes up two arguments: callback functions for success and failure cases of the Promise.then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
onFulfilled: a function called if the Promise is fulfilled. This function has one argument, the fulfillment value。通过 resolve() 触发。
onRejected: a function called if the Promise is rejected. This function has one argument, the rejection reason。通过 reject() 触发。
onFulfilled 和 onRejected都接受Promise对象传出的值作为参数,即下面的 value 或 reason。
tips:then must return a promise.
promise2 = promise1.then(onFulfilled, onRejected);
- If either
onFulfilledoronRejectedreturns a valuex, run the Promise Resolution Procedure[[Resolve]](promise2, x). - If either
onFulfilledoronRejectedthrows an exceptione,promise2must be rejected witheas the reason. - If
onFulfilledis not a function andpromise1is fulfilled,promise2must be fulfilled with the same value aspromise1. - If
onRejectedis not a function andpromise1is rejected,promise2must be rejected with the same reason aspromise1.
The Promise Resolution Procedure
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.
To run [[Resolve]](promise, x), perform the following steps:
- If
promiseandxrefer to the same object, rejectpromisewith aTypeErroras the reason. - If
xis a promise, adopt its state- If
xis pending,promisemust remain pending untilxis fulfilled or rejected. - If/when
xis fulfilled, fulfillpromisewith the same value. - If/when
xis rejected, rejectpromisewith the same reason.
- If
- Otherwise, if
xis an object or function,- Let
thenbex.then. - If retrieving the property
x.thenresults in a thrown exceptione, rejectpromisewitheas the reason. - If
thenis a function, call it withxasthis, first argumentresolvePromise, and second argumentrejectPromise, where:- If/when
resolvePromiseis called with a valuey, run[[Resolve]](promise, y). - If/when
rejectPromiseis called with a reasonr, rejectpromisewithr. - If both
resolvePromiseandrejectPromiseare called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. - If calling
thenthrows an exceptione,- If
resolvePromiseorrejectPromisehave been called, ignore it. - Otherwise, reject
promisewitheas the reason.
- If
- If/when
- If
thenis not a function, fulfillpromisewithx.
- Let
- If
xis not an object or function, fulfillpromisewithx.
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
const p1 = new Promise((resolve, reject) => {
resolve('Success!');
// or
// reject(new Error("Error!"));
});
p1.then(value => {
console.log(value); // Success!
}, reason => {
console.error(reason); // Error!
});
// 要看一下
Promise.resolve('foo')
// 1. Receive "foo", concatenate "bar" to it, and resolve that to the next then
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. receive "foobar", register a callback function to work on that string
// and print it to the console, but not before returning the unworked on
// string to the next then
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string); // foobarbaz
}, 1)
return string;
})
// 3. print helpful messages about how the code in this section will be run
// before the string is actually processed by the mocked asynchronous code in the
// previous then block.
.then(function(string) {
console.log("Last Then: oops... didn't bother to instantiate and return " +
"a promise in the prior then so the sequence may be a bit " +
"surprising");
// Note that `string` will not have the 'baz' bit of it at this point. This
// is because we mocked that to happen asynchronously with a setTimeout function
console.log(string); // foobar
});
// logs, in order:
// Last Then: oops... didn't bother to instantiate and return a promise in the prior then so the sequence may be a bit surprising
// foobar
// foobarbaz
二、Promise.prototype.catch()
The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise.prototype.then(undefined, onRejected) (in fact, call obj.catch(onRejected) internally calls obj.then(undefined, onRejected)). This means that you have to provide an onRejected function even if you want to fall back to an undefined result value - for example obj.catch(() => {}).
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
上面代码中,一共有三个 Promise 对象:一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。
一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
上面代码中,someAsyncThing()函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。
一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
上面代码运行完catch()方法指定的回调函数,会接着运行后面那个then()方法指定的回调函数。如果没有报错,则会跳过catch()方法。
const p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
throw new Error('oh, no!');
}).catch(function(e) {
console.error(e.message); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
// Throwing an error will call the catch method most of the time
const p1 = new Promise(function(resolve, reject) {
throw new Error('Uh-oh!');
});
p1.catch(function(e) {
console.error(e); // "Uh-oh!"
});
// Errors thrown inside asynchronous functions will act like uncaught errors
const p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw new Error('Uncaught Exception!');
}, 1000);
});
p2.catch(function(e) {
console.error(e); // This is never called
});
// 如果 promise 状态已经变成 resolved,再抛出错误是无效的。
const p3 = new Promise(function(resolve, reject) {
resolve();
throw new Error('Silenced Exception!');
});
p3.catch(function(e) {
console.error(e); // This is never called
});
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
(async () => f())()
.then(...)
需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。
(async () => f())()
.then(...)
.catch(...)
第二种写法是使用new Promise()。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
二、async 函数(需要嵌套吗?)
async 函数内部允许使用 await 关键字,async 和 await 关键字可以以一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 Promise。
async 函数可能包含 0 个或多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并让出其控制权,只有当其等待的基于 Promise 的异步操作被兑现或被拒绝后才会恢复进程。Promise 的解决值(resolve 的内容)会被当做该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。
await 关键字只在 async 函数内有效
async 函数一定会返回一个 Promise 对象。如果一个 async 函数的返回值看起来不是 Promise,那么它将会被隐式地包装在一个 Promise 中。即async隐式返回Promise作为函数的返回值。
async function foo() { return 1 } // 等价于 function foo() { return Promise.resolve(1) }
async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。
async function foo() { await 1 } // 等价于 function foo() { return Promise.resolve(1).then(() => undefined) }
在 await 表达式之后的代码可以被认为是存在在链式调用的 then 回调中,多个 await 表达式都将加入链式调用的 then 回调中,返回值将作为最后一个 then 回调的返回值。