阅读本篇文章之前一定要明白异步解决方案和 http 的关系。异步是异步,http 是http。http 请求是一个异步的过程,而异步并不一定就和 http 请求联系在一起。所以不要提起异步就默认为就是 ajax请求数据。
Promise
Promise 对象用于表示一个异步操作的最终状态(玩成或失败),以及其返回的值。
看下控制台输出的 Promise 对象信息:
Promise 的构造函数接受一个函数作为参数。传入的函数参数可以有两个: resolve 和 reject。resolve 是将 Promise 从 pending 状态置为 fulfilled 状态。 reject 是将 Promise 状态从 pending 置为 rejected 状态。我们可以简单的理解成:resolve 表示异步操作成功后的回调函数, reject 表示异步操作失败后的回调函数。
Promise 最直接的好处就是链式调用。
没有使用 Promise 来实现异步操作:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
按上面的写法,做很多的多层回调会让我们陷入经典的回调地狱。
而使用 Promise 来实现异步多层回调:
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
对比于我们可能会陷入的回调地狱,Promise 简化了层层回调的写法。而且,用维护状态、传递状态的方式来使得回调函数能够及时调用,这比传递 callback 函数要简单、灵活的多。
在 then 方法中,我们除了可以 return 一个 Promise 对象,还可以直接 return 数据,可以在之后的 then 中接收到 return 数据了:
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1执行完成');
resolve('随便什么数据1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务2执行完成');
resolve('随便什么数据2');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data + '@@');
return runAsync2();
})
.then(function(data){
console.log(data + '##');
return '直接返回数据'; //这里直接返回数据
})
.then(function(data){
console.log(data + '--');
});
上面的例子只是讲了 Promise 的用法,其中涉及到的只有成功时回调的 resolve,那失败状态又是如何使用的呢?
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的随机数
if(num<=5){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason, data){
console.log('rejected');
console.log(reason);
}
);
我们在 getNumber 函数中调用 Promise 中的 reject 来将某种失败的状态传递出来,然后在 then 中传递了两个参数。 then 方法可以接受两个参数,第一个对应 resolve 的回调,第二个对应 reject 的回调。所以在这两个回调函数中,我们可以获取对应的成功或失败返回的数据。
我们常用的 Promise 对象除了 then 方法外,还有一个 catch 方法。该方法是用来干啥的呢?then 里的参数是可选的, catch(failureCallback)
是 then(null, failureCallback)
的缩略形式。catch 的作用其实和 then 的第二个参数一样,用来指定 reject 的回调:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
catch 除了可以用来表示 reject 的回调外,它还有另外一个作用:在执行 resolve 的回调(也就是上面代码 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 Js, 而是会进到这个 catch 方法中。
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
Promise.all()
Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
用 Promise.all 来执行,all 接收一个数组参数,里面的值最后都会返回 Promise 对象。这样,三个异步操作并行执行,等到它们都执行完后才会进到 then 里面。
Promise.race()
race 意为 “竞争”。 all 方法的效果实际上是谁跑的慢,以谁为准执行回调,进而达到一个并行的效果。race 的效果则和 all 的效果相反,谁跑的最快就先回调执行谁。
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
用 Promise.race 来执行,race 接收一个数组参数,数组里面的值都是可以最终执行返回 Promise 对象。调用执行的时候都是并行执行的,一旦数组参数中的某个对象执行完并返回 Promise 对象就会立即进入 then 方法继续下一步。而后面数组其他的对象执行完依次重复该过程,体现了一个竞速的效果。
Async/await
async/await
是 es7 提出的异步特性
async
字面意思是“异步”,用于声明一个函数是异步的。await
的字面意思是“等待”,用来等待异步函数完成。
通常来说async/await
都是跟随 Promise
一起使用的。因为async
返回的是一个 Promise
对象。
/**
* 成功执行
*/
async function funcSuccess() {
const count = 10;
return count;
}
funcSuccess().then(
(res) => {
console.log(res);
}
);
/**
* 失败执行
*/
async function funcFail() {
const count = 10 + i;
console.log('测试是否继续执行');
return count;
}
funcFail()
.then(
(res) => {
console.log('执行成功', res);
}
)
.catch(
(error) => {
console.log('执行失败', error);
}
);
如果 async 函数执行顺利并结束,返回的 Promise 对象的状态会从等待状态转变成功状态,并输出 return 命令返回的结果(没有则为 undefined)。如果 async 函数执行途中失败,JS 会认为 async 函数已经完成执行,返回的 Promise 对象的状态会从等待转变成失败,并输出错误信息。
await
只能用在async
函数里面,存在于 async 内部的普通函数也不行。
/**
* await 只能用在 async 函数内部,存在于 async 函数内部的普通函数也不行
* 下面这段代码会直接报错
*/
async function testA() {
function abc () {
const ab = await new Promise(resolve => {
setTimeout(() => {
resolve(10);
}, 2000);
});
}
}
引擎会统一将 await 后面的跟随值视为一个 Promise, 对于不是 Promise 对象的值会调用 Promise.resolve() 进行转化。即便此值为一个 Error 实例,经过转化后,引擎依然视其为一个成功的 Promise, 其数据为 Error 的实例。
当函数执行到 await 命令时,会暂停执行并等待其后的 Promise 结束。如果该 Promise 对象最终成功,则会返回成功的返回值,相当于将 await xxx 替换成 返回值。如果该 Promise 对象最终失败,且错误没有被捕获,引擎会直接停止执行 async 函数并将其返回对象的状态更改为失败,输出错误信息。
async 函数中的 return x 表达式,相当于 return await x 的简写。
/**
* await 后面执行成功
*/
async function awaitFuncSuccess() {
const n1 = await 10;
const n2 = await new Promise<number>((resolve) => {
setTimeout(() => {
resolve(20);
}, 2000);
});
console.log('n1', n1, 'n2', n2);
return n1 * n2;
}
awaitFuncSuccess()
.then(
(res) => {
console.log('执行成功', res); // 约两秒后 输出 200
}
)
.catch(
(error) => {
console.log('error', error);
}
);
/**
* await 后面执行失败
*/
async function awaitFuncFail() {
const n1 = await 10;
console.log('n1', n1);
const n2 = await new Promise<number>((resolve, reject) => {
setTimeout(() => {
reject(20);
}, 2000);
});
console.log('n1', n1, 'n2', n2);
return n1 * n2;
}
awaitFuncFail()
.then(
(res) => {
console.log('执行成功', res);
}
)
.catch(
(error) => {
console.log('error', error); // 约两秒后 输出 20
}
);
顺序发生
/**
* 顺序执行
*/
async function queueFunc_A() {
const n1 = await createPromise();
console.log('n1', n1);
const n2 = await createPromise();
console.log('n2', n2);
const n3 = await createPromise();
console.log('n3', n3);
}
function createPromise() {
return new Promise((resolve) => {
setTimeout(() => {
setTimeout(() => {
resolve(10);
});
}, 2000);
});
}
async function queueFunc_B() {
for (let i = 0; i < 3; i ++) {
let n = await createPromise();
console.log('N' + (i + 1), n);
}
}
// 运行下面两个函数,都是间隔两秒依次输出
queueFunc_A();
queueFunc_B();
并发执行
仔细查看下面的代码是如何使用数组进行并行执行的
/**
* 并行执行
*/
async function parallelFunc_A() {
const res = await Promise.all([createPromise(), createPromise(), createPromise()]);
console.log('Data', res);
}
async function parallelFunc_B() {
let res = [];
const reqs = [createPromise(), createPromise(), createPromise()];
for (let i = 0; i < reqs.length; i++) {
res[i] = await reqs[i];
}
console.log('Data', res);
}
async function parallelFunc_C() {
let res = [];
let reqs = [1,2,3].map(
async (item) => {
let n = await createPromise();
return n + 1;
}
);
for (let i = 0; i < reqs.length; i++) {
res[i] = await reqs[i]
}
console.log('Data', res);
}
错误处理
一旦 await 后面的 Promise 转变成 rejected, 整个 async 函数便会终止。然而很多时候我们不希望因为某个异步操作的失败,就终止整个函数,因此需要进行合理错误处理。注意,这里所说的错误不包括引擎解析或执行的错误,仅仅是状态变为 rejected 的 Promise 对象
处理错误的方式有两种: 一种是对 Promise 对象进行包装,使其始终返回一个成功的 Promise 。 二是使用 try/catch 捕获错误。
/**
* 错误处理
*/
async function errorFunc_A() {
let n;
n = await createPromiseByParam(true);
return n;
}
async function errorFunc_B() {
let n;
try {
n = await createPromiseByParam(false);
} catch (e) {
n = e;
}
return n;
}
function createPromiseByParam(param: boolean) {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('出错啦!')
}, 1000);
});
return param ? p.catch((error) => { return 'catch error' + error}) : p;
}
// async 返回的是一个 Promise 对象,执行下面的函数获取结果
errorFunc_A().then(console.log);
errorFunc_B().then(console.log);