async function_仅仅20行代码就能实现async的原理你还不会吗?

ec70511eadedffd5e37d00f7d324bcb3.gif

背景

CodeReview中发现存在误用Promise, 对Promise相关callback的回调时机控制存在问题的场景

因此梳理了这份文档, 主要收集了常见的误用场景, 比较复杂的场景如何处理, 如何通过 Promise优化网页体验, 以及 async await 实现原理

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;}

上面的例子中,axios.post 执行后发出了异步请求,其链式调用 then 的回调函数是异步执行的, 因此

rollbackToTaskCenterModuleList 总是返回 undefined

可以对then的返回值使用await,就能实现我们想要的结果

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 都是异步执行的

7d8242ab6e86c9cd6e06cba3564c9bfb.png

代码地址 https://codesandbox.io/s/11-q2ub7

无意识地充分串行执行

开发中可能会出现 把可以并行执行的代码 串行执行

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,时间轴如下

6692e6b52c137793e9794d313c60a444.png

解决方法

1)为了将代码并行执行,可以的使用Promise.all的方式

const start = new Date();const [a, b, c] = await Promise.all([  getValue(),   getValue(),   getValue()]);console.log(new Date() - start); // 100

2)还可以 先初始化Promise,在需要获取值得地方 再await

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

5e9a9f8f6e8e2a1c5779e21a595351fe.png

为了测试每个值获取到的时间,https://codesandbox.io/s/23-d5ou1

7b08c816ceba1179dd33a6aa292aedcc.png

可以看到,Promise的执行时间虽然是固定的,但在变量赋值的时机上,前面的await会影响后面的await,整体结束的时间取决于时间最长的那个方法

b方法的执行时间虽然只有200,但是 受限于c的await,需要300ms后才能赋值给b

异步调用常规习惯是

  • 不要着急await

  • 延迟到要用 Promise 中保存的值时, 再 await

  • 将 Promise 想象成魔法师的帽子, 未来某个时刻可以掏出兔子, 但不一定要现在掏

可选await的场景(缓存 及其 初始化)

需求:某些功能的接口请求,期望能立即返回缓存数据,并显示出来,随后立即对数据进行更新

方法:对于已缓存数据,优先返回缓存数据,并对数据更新;对于未缓存的数据,先表现loading,请求接口并更新数据成功后,loading结束,并替换内容

在vue中我们可以使用如下伪代码来实现这个功能

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简单介绍

function *get() {    yield fetch('/');    yield fetch('/a');    return 1;}

这是一个典型的generator函数,使用方法

var g = get();g.next();  // {value: Promise, done: false}g.next();  // {value: Promise, done: false}g.next();  // {value: 1, done: true}

将generator函数编译成es5代码

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代码

async function get() {    await fetch('/');    await fetch('/a');    return 1;}

转换后

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 函数的大概内容如下

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 的区别

结论:async 函数是 generator的语法糖,优点是能够自动调用next

generator函数的简单实现

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                    };            }        }    }};

如果是并行执行

async function get() {   const f1 =  fetch('/');   const f2 =  fetch('/a');  await f1;  await f2;    return 1;}

转换后

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];            }        });    });}

比较转换后的代码差异

04b3d110a174f27f16b93cd44282ab11.png

可以看到串行时,f1先执行,并且订阅了f1的执行完成事件,完成时 ,再调用f2,订阅f2的执行完成事件,再return 1;

与此相对并行时,f1和f2先后一起执行,订阅f1的执行完成事件,完成时,再调用f2的执行完成事件,在return 1。

关注「前端的神奇小屋」

做有情怀的前端人

df2959bc146bf8330d9ee163c2fd4c1e.gif

好文章,我在看❤️

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值