V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
cstome
V2EX  ›  JavaScript

ES 中要用 await,上一层的函数都要是 async 的?

  •  
  •   cstome · 2019-01-02 14:25:02 +08:00 · 7335 次点击
    这是一个创建于 1912 天前的主题,其中的信息可能已经有所发展或是发生改变。
    async function A() {
        let someData = await B();
        
        return someData;
    }
    
    async function B() {
        let someData = await C();
        
        //Some logic code
        return someResult;
    }
    
    async function C() {
        return new Promise();
    }
    
    A();
    

    上面实例的,只有 C 是异步的,B 在调用 C 同步执行的时候,B 必须是异步函数。而 A()在调用 B 时需要 B 通过 C 的返回经过 B 的某些计算,再返回给 A,因此调用 B 的时候也要是同步的。想要让 B 同步,A 就必须是异步函数。

    这样的话岂不是想要用 await,上层所有函数都必须是异步函数?

    48 条回复    2019-01-29 15:53:41 +08:00
    janxin
        1
    janxin  
       2019-01-02 14:32:12 +08:00
    当然不是,你还是可以用 promise 的那种写法,不需要 async 传染
    oyosc
        2
    oyosc  
       2019-01-02 14:38:20 +08:00
    async 返回的就是一个 promise 对象,你也可以直接 then 来写
    cstome
        3
    cstome  
    OP
       2019-01-02 14:42:38 +08:00
    @janxin #1
    @oyosc #2

    我知道,但是这样似乎还是没法优雅的解决回调的问题。
    janxin
        4
    janxin  
       2019-01-02 14:46:43 +08:00
    @cstome promise 不就是为了解决回调提出来的方法么?
    oyosc
        5
    oyosc  
       2019-01-02 14:47:22 +08:00
    @cstome 相对于回调地狱这种来说,已经很优雅了,更直观的表示,如果你是指完全的同步这种问题,那应该是没有...
    lrz0lrz
        6
    lrz0lrz  
       2019-01-02 14:49:53 +08:00
    是这样的,async 是用来指定 await 的范围,如果没有 async,js 就不知道哪些代码需要等待 await 执行完成。

    另外楼主这个例子,a 依赖 b 的结果,b 的结果依赖 c 的结果,c 的结果依赖异步的结果,所以 a 依赖异步的结果,理应是异步的呀?
    shintendo
        7
    shintendo  
       2019-01-02 14:52:05 +08:00
    你想要哪一层用 await,就在那一层用 async,与上一层无关,上一层仍然可以 promise
    如果你想要每一层都 await,自然每一层都要 async
    async/await 不过是语法糖,再牛逼也不能真的把异步变同步啊
    sagaxu
        8
    sagaxu  
       2019-01-02 14:52:42 +08:00 via Android
    await 只能出现在 async 函数內,但是普通函数可以调用 async 函数
    geelaw
        9
    geelaw  
       2019-01-02 14:53:22 +08:00 via iPhone   ❤️ 2
    async 的作用是启用该 context 内的自动 CPS 变换(也就是同步风格代码翻译成异步),await 的作用是表明这里是一个 CPS 变换的 checkpoint。

    用同步的风格写异步的代码 = 用 await,从而包裹之的 function 必须用 async 修饰(也就是“启用 await ”)。

    ES 的 async/await 和 C# 的一样。
    CloudnuY
        10
    CloudnuY  
       2019-01-02 14:54:15 +08:00
    你让一个人帮你去楼下买东西,你必须在楼上等着他买回来,总不能自己出去逛街吧……
    autoxbc
        11
    autoxbc  
       2019-01-02 14:57:11 +08:00
    async 传染的本质:对于末端是异步的函数,在整个调用链上,从分界点(同步函数以同步形式调用异步函数)开始,到末端,要全部显式声明 async
    a -> b -> c(以同步形式调用 d) -> d(异步) -> e(异步) -> f(异步)
    d,e,f 必须是 async ; a,b,c 完全不需要

    到底哪一层需要是异步函数,取决于程序到底可以在哪个位置并行,必然存在这么个分界点
    cstome
        12
    cstome  
    OP
       2019-01-02 15:00:10 +08:00
    @janxin #4
    @oyosc #5

    仔细想了一下,你们所说的改成 Promise 是怎么改?

    能拿我的例子改一下吗?

    改成这样?

    ```
    function A() {
    let someData = B().then(res => {
    return res;
    })
    }

    function B() {
    let someData = C().then(res => {
    //Some logic code
    return someResult;
    })
    }

    async function C() {
    return new Promise();
    }

    A();
    ```
    wyz123723
        13
    wyz123723  
       2019-01-02 15:01:36 +08:00
    看你是想写成异步还是同步了。想写成同步,也就是下一句必须等待上一句执行完毕才能执行,那就得用 await,也就必须用 async。如果你想写成异步,那就写成 then 的形式,也就不需要加 async 了。
    cstome
        14
    cstome  
    OP
       2019-01-02 15:03:46 +08:00
    @autoxbc #11 此时 c 要怎样用同步的方式调用异步?

    如果用 await 的话显然不行。
    zbinlin
        15
    zbinlin  
       2019-01-02 15:06:15 +08:00
    @cstome

    async function A() {
    return B();
    }

    async function B() {
    let someData = await C();

    //Some logic code
    return someResult;
    }

    async function C() {
    return new Promise();
    }

    A().then(..., ...);
    cstome
        16
    cstome  
    OP
       2019-01-02 15:08:18 +08:00
    @zbinlin #15 这样岂不是还是每一层都用 async
    zbinlin
        17
    zbinlin  
       2019-01-02 15:11:38 +08:00
    @cstome

    function A() {
    return B();
    }

    async function B() {
    let someData = await C();

    //Some logic code
    return someResult;
    }

    function C() {
    return new Promise();
    }

    A().then(..., ...)
    cstome
        18
    cstome  
    OP
       2019-01-02 15:12:05 +08:00
    @wyz123723 #13 我就是想要同步的。

    比方说我用 axios 请求数据,必须根据请求结果才能进行判断,执行下一步。

    如果用 Promise 方法就只能一直 then 下去,感觉整个程序都是写在 then 里,不优雅。

    然而用了 async/await 发现这个问题。
    zbinlin
        19
    zbinlin  
       2019-01-02 15:13:02 +08:00
    @zbinlin 只要函数内有 await 才必须使用 async 定义
    autoxbc
        21
    autoxbc  
       2019-01-02 15:20:28 +08:00
    @cstome #14

    如果 c 不需要 d 的返回值(既不需要异步状态的真实返回值,也不需要同步状态的 promise ),c 就是分界点

    async function d(){}

    function c(){
    d();
    }

    你的例子无法改写,分界点在更高的位置。上面说用 promise 改写的理解有误,用了 async 和 await 就不应在调用链里写任何 promise
    cstome
        22
    cstome  
    OP
       2019-01-02 15:21:27 +08:00
    @zbinlin #19 加入我在 A 里面也需要获取 B 的结果在进行处理呢?

    还是不可避免的要把 A 变成异步函数。

    又或者使用 Promise 的话,就只能把后面的逻辑都写在 then 里:

    ```js
    function A() {
    B().then(res => {
    //some logic
    return someResult;
    })
    }
    ```

    这样看起来就是不太好。。。
    janxin
        23
    janxin  
       2019-01-02 15:34:48 +08:00
    @cstome 这就是显示切换的缺点,想兼容不想大改只有用这种方式
    zbinlin
        24
    zbinlin  
       2019-01-02 15:48:08 +08:00
    @cstome 使用 async 函数有什么影响吗(不好的地方)?
    jin5354
        25
    jin5354  
       2019-01-02 15:56:05 +08:00
    @cstome C 是个异步函数,B 调用了 C 且依赖 C 的返回值,那 B 肯定也是异步函数啊,同理 A,async/await 关键字就是在表明本函数是异步函数,但是可用类同步的姿势写,不可能跟同步写的一模一样的,设计出来就不是完全无感知的,你原文写法没毛病
    shintendo
        26
    shintendo  
       2019-01-02 16:00:36 +08:00
    A 是否要等待 B 的结果,和 B 是否要等待 C 的结果,是两个不相关的异步事件,你想把哪个写成同步,就对哪个用 async/await,不能指望写了其中一个,另一个也自动变成同步了呀。
    otakustay
        27
    otakustay  
       2019-01-02 16:37:13 +08:00
    async 就是个标记,当你需要等一个异步函数的时候,无论它的调用方是不是 async,都注定是异步了(用 promise 的 then 也一样是异步)
    abc635073826
        28
    abc635073826  
       2019-01-02 16:44:36 +08:00
    @CloudnuY 只有你说到了重点🌚
    cstome
        29
    cstome  
    OP
       2019-01-02 18:03:43 +08:00
    @abc635073826 #28
    @CloudnuY #10

    JS 的逻辑还就真是买东西的 Promise 把东西买回来就行,你爱上哪逛上哪逛。
    cstome
        30
    cstome  
    OP
       2019-01-02 18:05:10 +08:00
    @zbinlin #24 就是这样的话几乎所有函数都是 async,或者 Promise,感觉都不太好。
    lzvezr
        31
    lzvezr  
       2019-01-02 18:29:44 +08:00 via Android
    你可以直接 return 一个 promise,在 then 里面 return 最终会被捕获
    ```评论不支持 markdown
    function A() {
    return B().then(res => {
    //some logic
    return someResult;
    })
    }
    await A()
    最终得到的是 someResult
    shynome
        32
    shynome  
       2019-01-02 18:30:28 +08:00 via Android
    别用 async 和 Promise 了, cb 一直写下去吧,性能又好

    对的就是有传染性,就是要 async 的
    ayase252
        33
    ayase252  
       2019-01-02 18:39:45 +08:00 via iPhone
    一个函数里有一个异步操作就是异步啊,没毛病啊。
    sagaxu
        34
    sagaxu  
       2019-01-02 19:33:12 +08:00 via Android
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function

    async function expression used as an IIFE,你可以用 IIFE 斩断这种传染性。
    sagaxu
        35
    sagaxu  
       2019-01-02 19:38:40 +08:00 via Android
    function a() {
    (async () => {
    // 此处可以 await
    })();
    }

    a 是一个没有 async 的函数,a 里面使用了 await
    zbinlin
        36
    zbinlin  
       2019-01-02 19:47:33 +08:00
    @cstome 没什么不好的,看多了就习惯了,真的。
    kcats
        37
    kcats  
       2019-01-02 19:51:04 +08:00
    tc39 有个新的提议: top level await, 参见 https://github.com/tc39/proposal-top-level-await , 目前是 stage 2, 最新版 v8 已经实现了这个 proposal, 所以你可以在 chrome 控制台直接写 await 1 回车测试.
    kcats
        38
    kcats  
       2019-01-02 19:52:23 +08:00
    @kcats 不过好像和楼主的问题无关...
    des
        39
    des  
       2019-01-02 22:53:57 +08:00 via Android
    虽然不是上层必须 async,但还是 async 舒服。
    不传染的话,你就得自己等待返回结果,然后处理

    顺便提一下一个有意思的东西,fibjs,就是为了解决这些的
    TwoDays91
        40
    TwoDays91  
       2019-01-03 08:11:00 +08:00 via iPhone
    如果你逻辑依赖异步那你只能这么写,你还没试过每个 async 都要写 try catch。建议风格统一不要 promise 混着用。
    jjx
        41
    jjx  
       2019-01-03 08:16:06 +08:00
    @kcats

    deno 有一个目标也是这个

    await 传染很容易出 bug, 不过大都可以在测试层面解决掉
    DOLLOR
        42
    DOLLOR  
       2019-01-03 08:33:56 +08:00 via Android
    callback, Promise, async function 都有传染性,只是语法糖甜度不同,该异步的仍要异步
    abc635073826
        43
    abc635073826  
       2019-01-03 15:12:02 +08:00
    @cstome 本质上东西回来了你是要拿到的,它总有一个归属地
    kcats
        44
    kcats  
       2019-01-03 15:16:08 +08:00
    @jjx 这是正常的吧, 一个异步的函数在外部还能搞成同步的? 那异步的作用有啥意义? 楼主的问题是自己没想清楚, top level await 解决的问题是在没有 async 函数标记的情况下同步写异步代码, 和楼主的问题是两码事.
    cstome
        45
    cstome  
    OP
       2019-01-03 15:36:39 +08:00
    @kcats #44 其实我就是想把异步的强制变同步。

    说实话,在大部分编程中,同步的情况比异步要多。

    只不过 JS 是动不动都是异步的。。。
    kcats
        46
    kcats  
       2019-01-03 16:24:03 +08:00
    @cstome 这个只是语法(内部处理机制)上的不同. 之前有和一个做 cdn 的哥们讨论过, 他习惯于写 go 和 lua, 这两个语言都有 routine 的概念, io 操作都是异步的, 但是都是同步的写法. 这和 js 就是两个极端. js 本质上暴露给用户的就是一个纯异步的环境, async/await 只是一个语法糖, 把回调变成的同步的写法. 但是本质是没有变的. 因为 js 的函数栈与事件循环机制, 决定了只有一个调用栈被清空了之后, 才能够执行下一个事务. async/await 本质上还是回调, 相当于是把后续的代码分块了, 和 python 的 event 有点类似. 说白了 async/await 就是提供了一个标记, 既是给开发人员看的, 也是给解释器看的. 如果没有这个标记还要实现相同的效果, 那整个 js 的机制和 API 规范都要改了.
    Sapp
        47
    Sapp  
       2019-01-24 10:17:52 +08:00
    同步如何能拿到异步的返回值?要么回调,要么把异步转为同步啊... 你的 b 一旦调用了 c,他就是个异步了,a 调用 b 也成了异步,你想拿到 a 返回的值,必然要么回调,要么把 a 转同步
    libook
        48
    libook  
       2019-01-29 15:53:41 +08:00
    因为 A 必须依赖 B 执行完才可以继续执行,同时 B 也依赖 C 执行完才能继续执行,所以不管你用 callback 还是 Promise 还是 async,都逃不掉三者都做同步化处理:

    callback 版本:

    function A(resultFromB) {
    let someData = resultFromB;
    return someResult;
    }

    function B(resultFromC, cb) {
    let someData = resultFromC;

    //Some logic code
    cb(someResult);
    }

    function C(cb) {
    (new Promise()).then((result) => {
    cb(result, A);
    });
    }

    C(B);

    Promise 版本:

    function A(resultFromA) {
    let someData = resultFromA;

    return someData;
    }

    function B(resutlFromC) {
    let someData = resutlFromC;

    //Some logic code
    return someResult;
    }

    function C() {
    return new Promise();
    }

    C.then(B).then(A).then((resultFromA) => {
    //Do something.
    });

    避免不了的,但是外层层都用 async 不是因为内层用了 async,而是因为外层关心内层执行完的结果,如果不关心的话完全可以不用 async。

    function B() {
    new Promise();
    }

    function A() {
    B();//我不关心 B 执行完返回啥,就让他自生自灭吧
    //继续执行其他的代码
    }

    A();
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1204 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 23:15 · PVG 07:15 · LAX 16:15 · JFK 19:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.