await原理 js_promise 和 async await 的联系 常见使用问题 实现原理

182989a13c486afc8462664c4bf49546.png

## 背景
CodeReview中发现存在误用Promise, 对Promise相关callback的回调时机控制存在问题的场景
因此梳理了这份文档, 主要收集了常见的误用场景, 比较复杂的场景如何处理, 如何通过 Promise优化网页体验, 以及 async await 实现原理
```javascript
export async function getStaticConf() {
let rollbackToTaskCenterModuleList;
axios.post('/rest/wd/kconf/get', {
key: 'frontend.browserConfig.lowActiveConfig',
type: 'json',
}).then( res => {
rollbackToTaskCenterModuleList = res.data.rollbackToTaskCenterModuleList;
}, () => {
rollbackToTaskCenterModuleList = false;
});
return rollbackToTaskCenterModuleList;
}
```
上面的例子中,axios.post 执行后发出了异步请求,其链式调用 then 的回调函数是异步执行的, 因此
rollbackToTaskCenterModuleList 总是返回 undefined
可以对then的返回值使用await,就能实现我们想要的结果
```javascript
export async function getStaticConf() {
let rollbackToTaskCenterModuleList;
await axios.post('/rest/wd/kconf/get', {
key: 'frontend.browserConfig.lowActiveConfig',
type: 'json',
}).then( res => {
rollbackToTaskCenterModuleList = res.data.rollbackToTaskCenterModuleList;
}, () => {
rollbackToTaskCenterModuleList = false;
});
return rollbackToTaskCenterModuleList;
}
```
### 更进一步: Promise 的各部分回调函数是异步执行的吗
new Promise 里的代码是同步的,then、catch、finally 都是异步执行的

a2db189200992f13ce9b0921ea9df868.png


代码地址 [https://codesandbox.io/s/11-q2ub7](https://codesandbox.io/s/11-q2ub7)
## 无意识地充分串行执行
开发中可能会出现 把可以并行执行的代码 串行执行
```javascript
const start = new Date();
const a = await getValue();
const b = await getValue();
const c = await getValue();
console.log(new Date() - start); // 300
```
假设 每个getValue 所需的时间都是100ms, 那么总共所需的时间为 300ms,时间轴如下

3fd0537cd964284046e3216a1416413a.png


解决方法
1)为了将代码并行执行,可以的使用Promise.all的方式
```javascript
const start = new Date();
const [a, b, c] = await Promise.all([
getValue(),
getValue(),
getValue()
]);
console.log(new Date() - start); // 100
```
2)还可以 先初始化Promise,在需要获取值得地方 再await
```javascript
const start = new Date();
const a1 = getValue();
const b1 = getValue();
const c1 = getValue();
const a = await a1;
const b = await b1;
const c = await c1;
console.log(new Date() - start); // 100
```
可以看到3个方法都是并行执行的,代码链接: [https://codesandbox.io/s/22-dn0le?file=/src/index.js](https://codesandbox.io/s/22-dn0le?file=/src/index.js)

8616fea3d5def3fb5eaab1fbea2b7c37.png


为了测试每个值获取到的时间,[https://codesandbox.io/s/23-d5ou1](https://codesandbox.io/s/23-d5ou1)异步调用常规习惯是

  • 不要着急await
  • 延迟到要用 Promise 中保存的值时, 再 await
  • Promise 是个魔术师的帽子,未来某个时刻可以掏出兔子, 但不一定要现在掏
  • 异步调用在完成后, 会把兔子偷偷的放到帽子里面去 ,后续任何时候都能够摸到那只兔子
    ## 可选await的场景(缓存 及其 初始化)
知乎视频​www.zhihu.com


需求:某些功能的接口请求,期望能立即返回缓存数据,并显示出来,随后立即对数据进行更新
方法:对于已缓存数据,优先返回缓存数据,并对数据更新;
对于未缓存的数据,先表现loading,请求接口并更新数据成功后,loading结束,并替换内容
在vue中我们可以使用如下伪代码来实现这个功能
```javascript
foo: function(...args) {
const params = 12222
const pms = ajax(params)
const update = (v) => this.xx = v
if (cache == null) {
cache = await pms
} else {
pms.then(res => update(res))
}
update(cache);
}
```
## async await 原理
### generator简单介绍
```javascript
function *get() {
yield fetch('/');
yield fetch('/a');
return 1;
}
```
这是一个典型的generator函数,使用方法
```javascript
var g = get();
g.next(); // {value: Promise, done: false}
g.next(); // {value: Promise, done: false}
g.next(); // {value: 1, done: true}
```
将generator函数编译成es5代码
```javascript
var __generator = () { ...}
function get() {
return __generator(function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch('/')];
case 1:
return [4 /*yield*/, fetch('/a')];
case 2:
return [2 /*return*/, 1];
}
});
}
```
### 尝试对async函数转换成es5代码
```javascript
async function get() {
await fetch('/');
await fetch('/a');
return 1;
}
```
转换后
```javascript
var __awaiter = () {...}
var __generator = () { ...}
function get() {
return __awaiter(function () {
return __generator(function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch('/')];
case 1:
return [4 /*yield*/, fetch('/a')];
case 2:
return [2 /*return*/, 1];
}
});
});
}
```
可以看到 主要是两个函数,__awaiter 还有__generator,__awaiter 函数的大概内容如下
```javascript
var __awaiter = function(_g) {
var generator = _g();
return new Promise(function(resolve) {
function fulfilled() {
step(generator.next());
}
function step(result) { // {value: 1, done: true}
result.done ? resolve(result.value) : Promise.resolve(result.value).then(fulfilled);
}
step(generator.next()); // {value: Promise, done: fales}
});
};
```
它的核心是 订阅Promise的执行完成时机,自动调用generator的next方法,这样就不用每次手动再调用next方法,使用的体验更好也是 async 和 generator 的区别
结论:<b>async 函数是 generator的语法糖,优点是能够自动调用next</b>
### generator函数的简单实现
```javascript
var __generator = function(body) {
var _ = {
label: 0,
}
return {
next: function() {
var op = body(_);
switch (op[0]) {
case 4:
_.label++;
return {
value: op[1],
done: false
};
case 2:
return {
value: op[1],
done: true
};
}
}
}
};
```
### 如果是并行执行
```javascript
async function get() {
const f1 = fetch('/');
const f2 = fetch('/a');
await f1;
await f2;
return 1;
}
```
转换后
```javascript
var __awaiter = () {...}
var __generator = () { ...}
function get() {
return __awaiter(function () {
var f1, f2;
return __generator(function (_a) {
switch (_a.label) {
case 0:
f1 = fetch('/');
f2 = fetch('/a');
return [4 /*yield*/, f1];
case 1:
return [4 /*yield*/, f2];
case 2:
return [2 /*return*/, 1];
}
});
});
}
```
比较转换后的代码差异

83eb813894c4678a374f7bcd1a3c5481.png


可以看到串行时,f1先执行,并且订阅了f1的执行完成事件,完成时 ,再调用f2,订阅f2的执行完成事件,再return 1;
与此相对并行时,f1和f2先后一起执行,订阅f1的执行完成事件,完成时,再调用f2的执行完成事件,在return 1。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值