关于 async function 的设计哲学:如何评价 await 一个 resolved 的 promise 会使上下文拆成两个 tick ? - V2EX
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 4942 次点击
    这是一个创建于 3241 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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-0-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     Solana     2990 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 13:17 PVG 21:17 LAX 05:17 JFK 08:17
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86