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

关于 async function 的设计哲学:如何评价 await 一个 resolved 的 promise 会使上下文拆成两个 tick ?

  •  
  •   dou4cc · 2017-02-13 21:05:54 +08:00 · 4252 次点击
    这是一个创建于 2621 天前的主题,其中的信息可能已经有所发展或是发生改变。
    49 条回复    2017-02-14 23:40:08 +08:00
    noli
        1
    noli  
       2017-02-13 21:17:23 +08:00 via iPhone
    要求进一步详细信息: 能不能添加一点代码示例来表达情况?
    dou4cc
        2
    dou4cc  
    OP
       2017-02-13 21:24:00 +08:00
    let s = 0;
    (async () => {
    await Promise.resolve();
    ++s; //2
    })();
    ++s; //1
    dou4cc
        3
    dou4cc  
    OP
       2017-02-13 21:25:16 +08:00
    我希望是这样:
    let s = 0;
    (async () => {
    await Promise.resolve();
    ++s; //1
    })();
    ++s; //2
    morethansean
        4
    morethansean  
       2017-02-13 21:27:56 +08:00
    ... 楼主, seriously?
    dou4cc
        5
    dou4cc  
    OP
       2017-02-13 21:30:02 +08:00
    ?
    dou4cc
        6
    dou4cc  
    OP
       2017-02-13 21:30:17 +08:00
    dou4cc
        7
    dou4cc  
    OP
       2017-02-13 21:30:34 +08:00
    mooncakejs
        8
    mooncakejs  
       2017-02-13 21:36:27 +08:00 via iPhone
    你需要一个全局 async
    sox
        9
    sox  
       2017-02-13 21:50:37 +08:00
    什么哲学,本来就该这样。。
    xcodebuild
        10
    xcodebuild  
       2017-02-13 22:12:57 +08:00
    你的期望就不该这么写。。
    zkd8907
        11
    zkd8907  
       2017-02-13 22:20:01 +08:00
    竟然想顺序执行,为啥还要用 await/async 。。。没明白你想表达啥。
    XDDD
        12
    XDDD  
       2017-02-14 00:14:34 +08:00 via iPhone
    async 的目的就是拆成两个 tick ,你的期望叫做 sync
    FrankFang128
        13
    FrankFang128  
       2017-02-14 00:16:57 +08:00   ❤️ 1
    你的期望是错的……
    dou4cc
        14
    dou4cc  
    OP
       2017-02-14 06:49:50 +08:00 via Android
    @sox @XDDD 我只是举个栗子——你有没有想过既然已经 resolved 了,干嘛还要拆 tick ,过多的 tick 影响性能
    dou4cc
        15
    dou4cc  
    OP
       2017-02-14 06:54:14 +08:00 via Android
    在某些场合我会对状态转移函数作缓冲,如果一个 tick 内又转移回来了便不触发状态改变的事件,上面的设计影响了我的缓存命中
    Sparetire
        16
    Sparetire  
       2017-02-14 07:59:25 +08:00 via Android
    异步是会传染的。。如果你希望全部按同步逻辑来写,那你应该用 async 包住整个逻辑
    noli
        17
    noli  
       2017-02-14 08:45:09 +08:00
    @doucc

    你期望的结果,跟你认为实际会发生的结果,在我认为的实际里面,实际上都有可能发生。
    当然,一个异步结果很大可能总是会比下一个同步结果来得慢,但也并不是完全不可能得到相反的快慢结果。

    所以你实际情景中遇到的问题是什么,请不要用自己的抽象来说明了,请直接一点吧。
    dou4cc
        18
    dou4cc  
    OP
       2017-02-14 09:06:05 +08:00
    @noli 什么叫很大可能? js 是单线程的,各语句的执行先后是确定的
    dou4cc
        19
    dou4cc  
    OP
       2017-02-14 09:08:15 +08:00
    @noli 我的缓存机制是这样的,每过一个 tick 就回收一些缓存,所以两次操作间隔的 tick 越少能命中的缓存就越多
    dou4cc
        20
    dou4cc  
    OP
       2017-02-14 09:09:30 +08:00
    我非常不理解 await 对 tick 的极大浪费
    noli
        21
    noli  
       2017-02-14 09:31:04 +08:00 via iPhone
    @dou4cc 你的缓存回收是纯内存操作?这个缓存回收是在异步操作里面完成?为什么要这样做?
    fszaer
        22
    fszaer  
       2017-02-14 09:32:27 +08:00
    请问以下例子与 po 主所示代码中的异同
    let s=0;
    (()=>{
    setTimeout(()=>s++,0);

    })()
    ++s;
    xialdj
        23
    xialdj  
       2017-02-14 09:33:45 +08:00 via iPhone
    a();p.then(b);c(); 如果 p 已经被 resovled 那楼主的理解是不是执行顺序为 a b c ? 这个问题本质上和 await 是一致的
    jkeylu
        24
    jkeylu  
       2017-02-14 09:51:13 +08:00
    楼主想要的是下面这样的效果吗?其实调用 async 函数本质就是返回一个 promise
    async function foo() {
    let s = 0;
    await (async () => {
    await Promise.resolve();
    ++s; //2
    })();
    ++s; //1
    }

    foo();
    lujinang
        25
    lujinang  
       2017-02-14 09:54:07 +08:00
    @dou4cc 你根本没理解 node 的异步机制
    morethansean
        26
    morethansean  
       2017-02-14 09:59:06 +08:00
    await 怎么对 tick 有什么浪费了……
    本质上这里涉及到的还是 Promise 的概念, Promise 的 then(f, r) 保证了 f 一定不是立即执行的(看起来像是异步),而是被立即入队。要是 then 一会儿是同步的一会儿是异步的,这对整个系统来说都是灾难好吗?
    你都知道 async 关键字的意义,那么如果一定要实现你想实现的,肯定要把你的整个状态机都包含在 async function 内部啊……你想要用 async 的语法糖来让你的代码更方便一些,一会儿又在语法糖外面抱怨语法糖。你所谓的性能影响是因为你用了错误的方式造成了一些“意料之外”的结果,光是 then 将 f 入队本身没有什么严重的开销。
    dou4cc
        27
    dou4cc  
    OP
       2017-02-14 10:39:29 +08:00
    @morethansean 我觉得 async function 应该节省 tick ,另实现一个时而异步时而同步的 then2 。健壮的程序可以应对时而同步时而异步的回调。
    dou4cc
        28
    dou4cc  
    OP
       2017-02-14 10:43:48 +08:00
    @morethansean tick 本身确实开销不大,但节省 tick 可以带来的好处很多,除了我说的缓存机制,还有很多卡 tick 的事可以做
    otakustay
        29
    otakustay  
       2017-02-14 11:03:10 +08:00   ❤️ 2
    jQuery 的 Deferred 最初就是你的这个模式,如果已经 resolved 则是同步的,否则会变成异步
    但是这显然是不行的,从语言来说最重要的是一致性,即一个 Promise 你不应该需要知晓其当前状态(你看 Promise 本身也没有一个字段让你读状态),其当前状态应该不会影响你的任何逻辑,所以无论是否 resolved 其都必须是异步的
    morethansean
        30
    morethansean  
       2017-02-14 11:13:07 +08:00   ❤️ 1
    @dou4cc 还是那句话啊,你大概在理解上有一点偏离了……

    Promise 的设计本来就是需要保证异步的,不然这对使用者来说有极大的不稳定性需要考虑过于复杂的情形,甚至在有些场景下这将对整个代码结构都带来灾难性的破坏。 Promise then 本来就不是阻塞的不是同步代码,本来就是你所谓的 "tick" 模式。

    then 把一个任务加入了队列,你的外部世界的语句本来就没处在 Promise 的范畴内,自然和这个任务也没有关系。

    我们来看看你想要的结果,你想要的是:

    - 对一个 Promise 调用 then(f),如果这个 Promise 已经 resolve 了,那么 f 是立即执行的而不是被加入队列等当前的任务完成再执行。

    如开头所说不这样的原因是,这让 Promise 变得不确定,实际生产中这会带来很大的问题( V2EX 上就会有一大堆人开始批判这个坑),没人会想用 Promise 。而且说实话,一个函数一会儿同步一会儿异步,这很怪异,我没见过。

    可是你偏想要这么做,要提供一个 then2 ,满足这样的效果可以么?可以,当然可以啊(扩展一个 Promise 类,保存 Promise 的状态,调用 then 的时候检查一下然后做处理。对于 await ,虽然可能需要自己写处理器 polyfill 一下,在真正调用 await 之前检查一下 Promise 的状态),可是你真的需要这么做么?这就是大家关注的问题所在,我们可能认为你本来并不需要这样做(比如楼上有人提到你可能需要一个全局的 async 等等),所以认为你的理解是有偏差的。

    你所谓的各种卡 "tick" 的好处,不是 then(f) 可以一会儿同步一会儿异步的理由。
    fds
        31
    fds  
       2017-02-14 11:28:53 +08:00
    前面 @jkeylu 说的对,想保证执行顺序就是

    let s = 0;
    await (async () => {
    await Promise.resolve();
    ++s; //1
    })();
    ++s; //2

    不过这跟 tick 没有关系,肯定是多个 tick 才能完成。
    Premature optimization is the root of all evil -- DonaldKnuth
    rogerchen
        32
    rogerchen  
       2017-02-14 11:33:45 +08:00
    明明是同步逻辑非要写成异步有什么办法。 await 了不交控制流出去难道阻塞着过年?
    dou4cc
        33
    dou4cc  
    OP
       2017-02-14 12:10:22 +08:00
    @otakustay 我和你对一致性的理解有分歧,我认为健壮的程序无需 then 旳异步保证、同异步无本质区别。更显然地如果 then 实现成我说的,改成现在的会很方便:
    Promise.all([p, new Promise(r => setTimeout(r, 0))]).then
    即可,而从现在的实现改成我说的,就只能放弃 async function 改用 generator function 了
    otakustay
        34
    otakustay  
       2017-02-14 12:14:57 +08:00
    @dou4cc 然而你想要的永远不会和任何语言的设计原则相符合,这就是现实
    otakustay
        35
    otakustay  
       2017-02-14 12:25:24 +08:00
    最简单的例子,把你的代码改成一个比较现实的代码:

    (async () => {
    let user = await fetchCurrentUserInfo();
    console.log(1);
    })();
    console.log(2);

    请问你认为这个代码应该是 1 - 2 还是 2 - 1 还是任意都可以?
    limhiaoing
        36
    limhiaoing  
       2017-02-14 13:11:58 +08:00 via iPhone
    @morethansean
    一会异步一会同步的你没见过不代表没有, C#的 async await 就是这样的。
    limhiaoing
        37
    limhiaoing  
       2017-02-14 13:21:39 +08:00 via iPhone
    C#之类的这么做是从性能的角度考虑已经完成的同步执行性能会更好,比如从 socket 读一段数据,这个操作可能可以立即完成也可能无法立即完成。对于可以立即完成的完全可以同步执行,对于无法立即完成的才异步。
    至于 node 为什么选择全异步就了解了。
    limhiaoing
        38
    limhiaoing  
       2017-02-14 13:22:20 +08:00 via iPhone
    @limhiaoing 打错,最后一句少打了个不字。
    morethansean
        39
    morethansean  
       2017-02-14 13:30:14 +08:00   ❤️ 1
    @limhiaoing 兄弟你要结合上下文不是这么断章取义的,我说的是作为 Promise 的一个 API , then 的执行一会儿是同步一会儿是异步。

    给你一个 API readFile 然后告诉你这个 API 有可能是同步的有可能是异步的?

    写 Promise 链的时候就不会关心你的业务逻辑具体是怎么执行的,你从缓存里直接读也好你发请求异步读也好,只是在我 method chain 中的一环而已我还要去关心你是同步异步的具体做了什么事?而且这里还并没有显示能够看到是同步还是异步的方法,只是做了一个函数调用而已。

    Promise 本来是用来干嘛的,要解决什么问题,所以他这么设计了,把不稳定性和混乱引进来强行可以卡 tick 做很多的事情,关键是这些事情本来不是这么去做的。
    morethansean
        40
    morethansean  
       2017-02-14 14:02:58 +08:00
    @limhiaoing 而且由于我不太清楚 C# 里面的 async/await 的实现,然而我简单地搜索了一下,包括 http://developer.51cto.com/art/201305/393992_all.htm 这篇文章,这样如果抛开 nodejs 和 C# 单 /多线程的区别的话,看起来并没有什么不同。文章中有提到 `await 关键处的代码片段是在线程池线程上执行` 并不是 GUI 线程。再者,搜了一下知呼,里面也提到 await 开始的那一刻 async 函数就返回了,其实都是编译器的语法糖而已。是我理解有问题吗……感觉不是很懂你的意思了……
    limhiaoing
        41
    limhiaoing  
       2017-02-14 14:52:02 +08:00 via iPhone
    @morethansean

    task 或者 future/promise 抽象并不一定要异步,可以是延后执行,现在在上班,晚上再具体回复你。
    dou4cc
        42
    dou4cc  
    OP
       2017-02-14 15:49:35 +08:00
    @otakustay 这两条语句间没有顺承关系,我觉得无所谓哪条先执行
    thuanqin
        43
    thuanqin  
       2017-02-14 16:14:00 +08:00
    可能编译器可以直接处理这种情况将异步代码转为同步代码执行,不过会带来增加编译时间等开销吧。
    jarlyyn
        44
    jarlyyn  
       2017-02-14 16:19:56 +08:00
    那么不想写异步代码楼主还是写 go 吧。
    zhouyg
        45
    zhouyg  
       2017-02-14 16:34:33 +08:00
    async 只是语法糖而已,本质还是 Promise ,既然是 Promise 那当然是异步的,因为 Promise 就是这么设计的。
    Clarencep
        46
    Clarencep  
       2017-02-14 18:05:04 +08:00
    limhiaoing
        47
    limhiaoing  
       2017-02-14 21:34:50 +08:00
    @morethansean
    ``` cs
    var task = Task.FromResult<int>(1 + 2);
    new Action(async () => {
    await task;
    Console.WriteLine("1");
    })();
    Console.WriteLine("2");
    // Output:
    // 1
    // 2
    ```
    ``` cs
    var task = Task.Delay(1); // delay 1ms
    new Action(async () => {
    await task;
    Console.WriteLine("1");
    })();
    Console.WriteLine("2");
    // Output:
    // 2
    // 1
    ```
    C#的 async 、 await 是这样的,立即可以完成(代码 1 )的,可以同步执行先输出 1 再输出 2 ,需要 1ms 才能完成的(代码 2 ),才必须异步先输出 2 再输出 1 。
    C#应该是最早使用 async 、 await 语法糖的语言,之后才被各语言争相效仿(如果有更早的请指正),这种允许同步执行的 await 也被证明设计上没有错误,所以 ES7 如果是强制异步的话,就是设计哲学的问题了。
    limhiaoing
        48
    limhiaoing  
       2017-02-14 21:42:00 +08:00
    @limhiaoing
    而关于 future/promise 用于延后执行,可以看下这个。
    http://en.cppreference.com/w/cpp/thread/async
    std::launch::deferred enable lazy evaluation
    xieranmaya
        49
    xieranmaya  
       2017-02-14 23:40:08 +08:00
    因为 Promise 的 resolved/rejected callback 是异步执行的。就将。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3648 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 04:22 · PVG 12:22 · LAX 21:22 · JFK 00:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.