Promise与async/await
Promise与async/await
Promise与async/await在于解决回调地狱而出现,他们两者有点不同,执行async函数返回的是Promise对象,而async相当于Promise的then。
Promise
使用 JavaScript 编写代码会大量的依赖异步计算,计算那些我们现在不需要但将来某时候可能需要的值。所以 ES6 引入了一个新的概念,用于更简单地处理异步任务:Promise。
Promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。如果我们兑现了我们的承诺,那结果会得到一个值。如果发生了问题,结果则是一个错误,一个为什么不能交付的借口。使用Promise的一个最佳例子是从服务器获取数据:我们要承诺最终会拿到数据,但其实总有可能发生错误。
一个Promise必然处于以下几种状态之一:
- 待定(pending) :初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled) :意味着操作成功完成。
- 已拒绝(rejected) :意味着操作失败。
下面是Promise的流程图:

Promise的用法
下面是Promise的基础用法,setTimeout模拟的是异步请求,因为setTimeout里调用了resolve,此时的Promise状态为resolved,请求成功之后执行then里面的内容。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
});
});
p1.then((res) => {
console.log('p1 res:' + res);
console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
});
}).then((res) => {
console.log('p2 res:' + res);
console.log(p2);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
reject('reject');
});
}).then((res) => {
console.log('p3 res:' + res);
console.log(p3);
});
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
reject('reject');
});
}).then((res) => {
console.log('p4 res:' + res);
console.log(p4);
}).catch((err) => {
console.log('p4 err:' + err);
console.log(p4);
});

以上是执行了resolve()的Promise,他们都去执行了回调函数then()。接下来我们看看reject()的例子
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('reject');
resolve('resolve');
});
}).then((res) => {
console.log('p1 res:' + res);
console.log(p1);
}).catch((err) => {
console.log('p1 err:' + err);
console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
throw('error');
}).then((res) => {
console.log('p2 res:' + res);
console.log(p2);
}).catch((err) => {
console.log('p2 err:' + err);
console.log(p2);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
throw('error');
});
}).then((res) => {
console.log('p3 res:' + res);
console.log(p3);
}).catch((err) => {
console.log('p3 err:' + err);
console.log(p3);
});
setTimeout(() => {
console.log(p3);
});

通过执行代码发现,p1 p2被执行了catch回调,原因就是Promise主动执行了reject()或者在同步代码里面抛出了异常。但是我们发现p3没有执行catch回调,并且打印出来的Promise p3状态为pending,说明Promise的异步代码里面抛出的异常不会被catch到。
接下来我们再来看看catch调用的调用:
const p1 = new Promise((resolve, reject) => {
throw('error');
}).then((res) => {
console.log('p1 res:' + res);
console.log(p1);
}).catch((err) => {
console.log('p1 err:' + err);
console.log(p1);
return 100;
}).then((res) => {
console.log('p1 res1:' + res);
console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
throw('error');
}).then((res) => {
console.log('p2 res:' + res);
console.log(p2);
}).catch((err) => {
console.log('p2 err:' + err);
console.log(p2);
throw('error');
}).then((res) => {
console.log('p2 res1:' + res);
console.log(p2);
}).catch((err) => {
console.log('p2 err1:' + err);
console.log(p2);
});

这里有个小细节,就是在执行catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilled的Promise,他会继续执行之后的then回调。
const p1 = new Promise(function(resolve, reject) {
resolve();
console.log('p1 resolve');
throw('error');
console.log('p1 error');
});
const p2 = new Promise(function(resolve, reject) {
reject();
console.log('p2 reject');
throw('error');
console.log('p2 error');
});

最后是抛出异常,在resolve()或reject()后面抛出的错误会被忽略,但是其他在抛出错误代码之上的代码还是会被执行。
下面是Promise的总结:
-
执行了
resolve(),Promise状态会变成fulfilled,即 已完成状态 -
执行了
reject(),Promise状态会变成rejected,即 被拒绝状态 -
Promise只以
第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected -
Promise的同步代码中有
throw的话,就相当于执行了reject() -
Promise里没有执行
resolve()、reject()以及throw的话,这个promise的状态也是pending -
基于上一条,
pending状态下的promise不会执行回调函数then() -
执行
catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilled的Promise -
Promise的异步代码中有
throw的话,Promise无法捕获,只能通过用try...catch包裹Promise解决 -
基于上一条,
pending状态下的promise不会执行回调函数then() -
在
resolve()或reject()后面抛出的错误会被忽略
async/await
async 函数是使用async关键字声明的函数。async 函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用Promise。
下面是async的基础用法:
async function async1 () {
console.log('async1 000');
let await1 = await 1000;
console.log('await1:' + await1);
console. log('async1 100');
let await2 = await Promise.resolve(2000);
console.log('await2:' + await2);
console. log('async1 200');
let await3 = await p1;
console.log('await3:' + await3);
console. log('async1 300');
let await4 = await async2();
console.log('await4:' + await4);
console. log('async1 400');
}
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
});
});
async function async2() {
console. log('async2 000');
return 400;
}
async1();

可以看到上面async1函数里的代码以同步的形式编写,但是具有异步行为。
如果await的异步代码出现异常则可以使用try/catch代码块捕获:
async function async2 () {
try {
let await1 = await p2;
} catch (err){
console.log('catch async2'); //catch async2
}
}
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('resolve');
});
});
async2();
-
async函数一定会返回一个Promise对象 -
如果一个
async函数的返回值看起来不是Promise,那么它将会被隐式地包装在一个Promise中 -
async函数可能包含0个或者多个await表达式 -
await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于Promise的异步操作被兑现或被拒绝之后才会恢复进程 -
Promise的解决值会被当作该await表达式的返回值 -
使用
async/await关键字就可以在异步代码中使用普通的try/catch代码块
以一道面试题结束
const async1 = async () => {
console.log('async1');
setTimeout(() => {
console.log('timer1');
}, 2000);
await new Promise(resolve => {
console.log('promise1');
})
console.log('async1 end');
return 'async1 success';
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(res => {
console.log(res);
return 5
})
.then(Promise.resolve(3))
.catch(4)
.then(res => console.log(res))
setTimeout(() => {
console.log('timer2');
}, 1000);
点击查看答案
'script start'
'async1'
'promise1'
'script end'
1
5
'timer2'
'timer1'
解析:
-
第一步代码开始,先声明了一个函数
async,还未执行,接着继续执行代码,遇到了script start,将其输出到控制台 -
执行
async1,程序跳去async里面,遇到了async1,将其输出到控制台 -
接着是
setTimeout,这时候开启一个计时器,2秒后会将setTimeout的回调放去宏任务执行 -
遇到关键字
await,await的是一个Promise,直接执行Promise,将promise1输出到控制台 -
接下来要留意下,代码里的
Promise是没有resolve()或reject()的,就是说await还一直在等待Promise -
到这一步,
async1里面的代码执行完了,接着执行剩下的代码,于是遇到了script end,将其输出到控制台 -
然后又遇到一个
Promise.resolve(1),接着把.then()放去微任务里面等待执行 -
接下来要回到
setTimeout,1秒后会将setTimeout的回调放去宏任务执行 -
这时候主线程的代码已经执行完了,就开始取出微任务里面的任务执行,即执行
.then(),而.then()所在的Promise已经resolve,并且返回了1,所以.then的res是1,将其输出到控制台 -
同理继续执行
Promise.resolve(3)所在的.then,又因为这个then没有return,所以将其上级的参数透传下去,故最后执行的.then的res是5,将其输出到控制台
最后,让我们一起加油吧!
参考资料:
转载自:https://juejin.cn/post/7213285858249195579