V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tomoya92
V2EX  ›  Node.js

一直有个疑问,用 nodejs 时有什么优雅的办法能让代码在流程上回避掉回调吗?

  •  3
     
  •   tomoya92 · 2019-01-15 22:41:36 +08:00 · 8345 次点击
    这是一个创建于 2139 天前的主题,其中的信息可能已经有所发展或是发生改变。

    虽然现在 ES 标准里加上了 async await 来实现同步操作,还有 Promise,但业务复杂了,代码还是会被各种回调弄的执行流程出问题

    举个例子

    写 js 的时候用的最多的应该就是回调了,回调里可以传很多个参数,简单的操作,这样写很方便,但业务复杂了,就不方便了,回调地狱也就是这样来的

    这时候你可能会说不是有 Promise 吗,但用这货我觉得也就是把代码变的好看些,拿结果还是要靠 then 方法回调拿

    到这你可能还会说,不是还有 async await 吗,这货确实不用从 then 函数的回调里拿数据了,但用 nodejs 多了就会发现,很多函数的调用的写法还是会用到回调,而且这时候还会在回调函数的前面加上个 async,我也是无语了,这不是又回到起点了吗,如下

    it ('waitBody', async function() {
    	await driver.sleep(500).wait('body', 30000).html().then(function(code) {
        	isPageError(code).should.be.false;
        })
    })
    

    当然也有用起来舒服的地方,比如 mongoose 的查询

    const results = await UserModel.find({});
    

    综上,难道就没有一个优雅的方法能让代码一行一行的执行吗,前一个结果执行完了,拿到结果再参与下一行代码的执行?

    有人会说了,你上面不是说了 async await 了吗,它不就是这样用的吗?那为啥还要在回调的方法上用 async await 呢?总觉得有点换汤不换药,折腾来折腾去,还是离不了回调,但回调又会涉及到代码结构和流程控制上的问题

    还请原谅我这小白的问题,相信很多学习 nodejs 的朋友都有过这样的疑惑 😂

    80 条回复    2019-12-28 23:03:12 +08:00
    oott123
        1
    oott123  
       2019-01-15 22:46:54 +08:00 via Android   ❤️ 1
    没有,这就叫异步,你可以改用 Python。
    sagaxu
        2
    sagaxu  
       2019-01-15 22:48:41 +08:00 via Android
    promise 不能 await?
    lekai63
        3
    lekai63  
       2019-01-15 22:51:14 +08:00 via iPhone
    async 都不能忍受的话 要不考虑学个正经的偏后端语言?
    misaka19000
        4
    misaka19000  
       2019-01-15 22:51:15 +08:00
    最好的办法就是不写 JS (滑鸡)
    EPr2hh6LADQWqRVH
        5
    EPr2hh6LADQWqRVH  
       2019-01-15 22:52:25 +08:00 via Android
    promisify
    tcdw
        6
    tcdw  
       2019-01-15 22:56:45 +08:00 via Android
    > 很多函数的调用的写法还是会用到回调

    https://nodejs.org/api/util.html#util_util_promisify_original
    Hanggi
        7
    Hanggi  
       2019-01-15 22:57:40 +08:00
    你这个写的感觉有问题。你再好好看看。
    tomoya92
        8
    tomoya92  
    OP
       2019-01-15 23:07:07 +08:00
    @tcdw #6 感谢大佬指点,原来 nodejs 内置已经实现了一个 promise,并且有一定的规则,谢谢
    wly19960911
        9
    wly19960911  
       2019-01-15 23:07:43 +08:00
    这个和 nodejs 有什么关系呢,那异步你怎么解决异步回调的问题? async 已经很接近同步的写法了。

    不是 nodejs 想这么做,而是逻辑上只有这样的处理啊,不断的回调,我知道你什么时候运行什么方法嘛?我只能等你运行完成之后调用我才知道,你可以选择同步,但是问题是同步又阻塞线程你满意嘛。。

    另外你用 await 就用错了,是我我会这么用,虽然我接触 await 是在 flutter 里面。

    it ('waitBody', async function() {
    var a = await driver.sleep(500);
    var b =await a.wait('body', 30000);
    await html();
    (function(code) {
    isPageError(code).should.be.false;
    })();
    })
    tomoya92
        10
    tomoya92  
    OP
       2019-01-15 23:08:22 +08:00
    @Hanggi #7 前端萌新,一直都是迷迷糊糊的用 nodejs,最近觉得非常有必要把这个弄清楚,所以才发帖求助的,还请见谅 :joy
    tomoya92
        11
    tomoya92  
    OP
       2019-01-15 23:10:09 +08:00
    @wly19960911 #9 感谢指点,上面那段代码不是我写出来的,是 uirecorder 录制完成后自动生成的测试用例里的代码,我也觉得在回调上还用 async await 总觉得别扭 :joy
    wly19960911
        12
    wly19960911  
       2019-01-15 23:10:44 +08:00
    async 和 await 是把一切耗时的操作打上一个 await,然后整体下来看起来就像同步,而不是像你那样 promise 和 async 混搭
    Pastsong
        13
    Pastsong  
       2019-01-15 23:13:44 +08:00 via Android
    js 语言特色就是单线程异步,你要先理解 async 解决了什么问题
    tomoya92
        14
    tomoya92  
    OP
       2019-01-15 23:14:56 +08:00
    @wly19960911 #12 await 不是等待的意思吗?加上这个关键字后,后面的代码要等待这行代码执行完才往下继续执行,不应该是这个意思吗?
    wly19960911
        15
    wly19960911  
       2019-01-15 23:18:55 +08:00   ❤️ 1
    @tomoya92 #14 是的,然后你就没必要去不断的 写个 promise.then()去处理回调了,把 promise.then()的东西丢到一个方法里面,一个个 await 调用,实际上会比 promise.then()好看,而且逻辑上清晰。async 没有解决什么根本上的问题,只是一种新的写法让异步回调看起来像同步,把箭头函数里面的东西丢到一个封装方法里面,最后到达一个更直观的代码.
    Sparetire
        16
    Sparetire  
       2019-01-16 01:55:05 +08:00 via Android
    没看懂都 await 了,为什么还要 then。。讲道理是不会出现一个 then 的
    autoxbc
        17
    autoxbc  
       2019-01-16 04:42:41 +08:00   ❤️ 1
    这个例子本身写的不好,看面条代码脑子绕圈是正常反应,要先坚实自己,就不会被带偏

    可以看 Promise 迷你书
    omnip
        18
    omnip  
       2019-01-16 04:51:51 +08:00
    试试这样
    const code = await driver.sleep(500).wait('body', 30000).html();
    isPageError(code).should.be.false;
    edward8628
        19
    edward8628  
       2019-01-16 05:31:48 +08:00
    async 和 await 是目前最优雅的了
    tomoya92
        20
    tomoya92  
    OP
       2019-01-16 08:01:07 +08:00 via iPhone
    @Sparetire 跟着网上的教程学呀,发现用上 await 就能同步了,就用上了
    motai
        21
    motai  
       2019-01-16 08:08:30 +08:00 via iPhone
    回调是 js 的精髓吧
    lzvezr
        22
    lzvezr  
       2019-01-16 08:24:24 +08:00 via Android
    回调是不可能避免的,只是封装成 promise,让.then 或者 await 调用而已
    .then 和 await 只是为了让代码变得好看
    比如
    await cb1()
    await cb2()
    await cb3()
    要比
    f()
    .then(cb1())
    .then(cb2())
    .then(cb3())
    或者
    f(cb3(cb2(cb1)))
    好看一些
    tomoya92
        23
    tomoya92  
    OP
       2019-01-16 08:58:16 +08:00
    @lzvezr #22 我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题
    des
        24
    des  
       2019-01-16 09:04:33 +08:00 via Android
    那么,你可以去看看 fibjs
    另外你可以用 promise 包装回调,然后 await
    然而我觉得是你没明白 promise 和 async 是做了什么
    shintendo
        25
    shintendo  
       2019-01-16 09:06:03 +08:00   ❤️ 1
    @tomoya92
    我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题
    ----------------------
    因为回调本来就不是问题啊,nodejs 的精髓你要把它“解决”掉,那为什么要用 nodejs 呢
    lhx2008
        26
    lhx2008  
       2019-01-16 09:08:36 +08:00 via Android
    还有一种方法是响应式编程,rxjs 或者 reactorjs,不过也只是看起来比较美好,await 那种还是好用一点
    tomoya92
        27
    tomoya92  
    OP
       2019-01-16 09:09:07 +08:00
    @des #24 我确实是糊里糊涂的
    tomoya92
        28
    tomoya92  
    OP
       2019-01-16 09:10:20 +08:00
    @lhx2008 #26 嗯,谢谢,我觉得我还是像楼上说的那样,先把 promise async await 它们都是做什么的弄清楚比较好
    woodensail
        29
    woodensail  
       2019-01-16 09:14:02 +08:00   ❤️ 1
    ps:async await 可不只是好看一点这么简单。
    async 给了你统一的错误流,以及可精确控制范围的 try catch。
    没有 async 前,写复杂异步链的异常处理非常之痛苦……
    learnshare
        30
    learnshare  
       2019-01-16 09:43:53 +08:00
    Promise + async/await 已经比较优雅了
    qiushijie
        31
    qiushijie  
       2019-01-16 09:47:54 +08:00 via Android
    异步适用一次回调,回调使用多次调用
    wunonglin
        32
    wunonglin  
       2019-01-16 10:00:28 +08:00
    用了 await 还用 then ??
    tomoya92
        33
    tomoya92  
    OP
       2019-01-16 10:02:28 +08:00
    @tcdw #6 大佬,再打扰一下,看你给的链接里的介绍,`fs.stat` 方法传进 `util.promisify(fs.stat) ` 里当参数后,下面就可以直接使用 async/await 来同步执行拿返回值数据了,当然它们有规则,就是 fs.stat 方法的回调第一个参数要是异常

    这样看来我是不是就可以理解为,任何一个被包装成了 Promise 的函数都可以使用 async/await 来同步执行拿返回值的数据?

    另外 `util.promisify()` 方法跟自定义的 Promise 封装是不是效果一样的呢?比如下面这两种用法是等效的吗?

    ```js
    function timeout(ms) {
    return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
    });
    }

    timeout(100).then((value) => {
    console.log(value);
    });
    ```

    ```js
    const timeout = util.promisify(setTimeout);
    timeout(100).then((value) => {
    console.log(value);
    });
    ```

    谢谢!
    cuberlzy
        34
    cuberlzy  
       2019-01-16 10:21:59 +08:00
    ``` js

    function timeout(ms) {
    return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
    });
    }

    await timeout(1000);

    ```
    roscoecheung1993
        35
    roscoecheung1993  
       2019-01-16 10:31:59 +08:00
    这就是 nodejs 的特点...如果没有异步,单线程模型就无法支持多用户访问了。
    某些模块的 api 还是支持同步调用的,比如 fs.readFileSync...写起来方便但是就阻塞了
    momocraft
        36
    momocraft  
       2019-01-16 10:36:01 +08:00
    代码清楚就行了,async/await 只是语法糖,不用太敏感

    await 之后的代码逻辑上已经是被 await 的 promise 的 then 的 callback,只是看起来代码是“连续”的
    yamedie
        37
    yamedie  
       2019-01-16 10:36:02 +08:00
    @tomoya92 "任何一个被包装成了 Promise 的函数都可以使用 async/await 来同步执行拿返回值的数据?"
    await 期待的就是一个 promise
    (如果 await 后面跟的是一个普通的 function, 也不会报错, 只是 await 变得毫无意义了)
    tomoya92
        38
    tomoya92  
    OP
       2019-01-16 10:39:14 +08:00
    @yamedie #37 哦哦,明白了,那我理解的就是对的了,谢谢!
    momocraft
        39
    momocraft  
       2019-01-16 10:41:24 +08:00
    顶楼的代码是不是也可以写成

    ```
    const code = await driver.sleep(500).wait('body', 30000).html();
    isPageError(code).should.be.false;
    ```

    ? 这样会清楚点吗?
    sagaxu
        40
    sagaxu  
       2019-01-16 10:48:34 +08:00 via Android
    @lzvezr 这段代码用回调写写看,就知道 await 是不是好看了

    for (let i = 0; i < 10; i++) {
    const ret = await $.getData(i);
    if (ret.code !== 0) {
    continue;
    }

    if (await check(ret.data) === true) {
    return ret.data;
    }
    }
    momocraft
        41
    momocraft  
       2019-01-16 10:52:18 +08:00   ❤️ 2
    await 一个不 thenable 的表达式也是有作用的,await 后的代码一定在栈清空后才被执行 (和 promise 的 then 同样保证)

    考虑:

    ```
    async function foo() {
    console.log(1);
    await 0;
    console.log(3);
    }

    foo();
    console.log(2);
    ```

    是不是有"意义"这个自己判断吧
    janxin
        42
    janxin  
       2019-01-16 10:53:20 +08:00
    @tomoya92 回调最大的问题难道不是很难看么
    momocraft
        43
    momocraft  
       2019-01-16 11:00:32 +08:00   ❤️ 1
    回调的另一个问题是这个语法不自带 "可组合性",表现为层数多起来就很难连异常处理一起写对

    而 await 时 promise fulfill 天然映射到表达式求值,promise reject 天然映射到求值中 throw,没被 catch 的 reject 会自动向上传递到一个无法忽略的地方
    tomoya92
        44
    tomoya92  
    OP
       2019-01-16 11:04:03 +08:00
    @janxin #42 还真不是,如果业务复杂,回调多了,会很乱的
    lzvezr
        45
    lzvezr  
       2019-01-16 12:48:06 +08:00 via Android
    @sagaxu 回调函数的循环控制嘛,这个在没有 await 的时候又不是没有

    实际上 await 后面是一个 promise 对象,而 promise 对象要返回值给上层需要一个 resolve()作为回调函数,最终还是回调函数

    这段程序要是没理解错的话,是需要依次获取数据直到得到正确结果
    可以构造一个对象,包含当前执行到的位置(i),最多可以达到的位置(10),函数 f(this.i),每次 f 执行之后 this.i++或者 cb(ret.data)
    Sparetire
        46
    Sparetire  
       2019-01-16 13:14:02 +08:00 via Android
    @tomoya92 不要把 async/await 当成同步。。它们只是看起来同步,实际上还是异步,异步逻辑不会消失,只会看起来变得优雅。把 async/await 当成同步那会给自己挖坑的。。
    sagaxu
        47
    sagaxu  
       2019-01-16 13:16:37 +08:00 via Android   ❤️ 1
    @lzvezr 手动维护一个 context 心智负担太重,这只是一个简单的例子,循环可以有多层,调用链路可以有十几层,实际逻辑可能复杂的多,相当于人肉维护一个状态机。同步写法,思路顺畅许多,也更不容易出错。

    不仅是 js,各种语言都在往弃回调,引入同步写法。
    tomoya92
        48
    tomoya92  
    OP
       2019-01-16 13:22:00 +08:00
    @Sparetire #46 也就是说用了 async/await 代码会在 await 那地方等着执行完对吧,至于被调用方法执行是同步执行还是异步执行的,不是 async/await 管的了,是这个意思不?
    lamada
        49
    lamada  
       2019-01-16 13:25:52 +08:00
    rxjs?
    lzvezr
        50
    lzvezr  
       2019-01-16 13:48:31 +08:00 via Android
    @sagaxu 嗯哼,好像理念并不冲突啊,谁不喜欢用同步写法去调用异步函数呢

    楼主的问题是觉得已经有 async 了,结果使用第三方库只是回调函数从 function 变成了 async function,并没有解决回调

    当其他库没法直接用,或者为了兼容性还在使用回调的时候,自己封装一下就显得很必要
    reus
        51
    reus  
       2019-01-16 13:57:51 +08:00
    不要用 js 不就行了,找个对并发支持更好的不就行了
    Sparetire
        52
    Sparetire  
       2019-01-16 14:03:31 +08:00 via Android   ❤️ 1
    @tomoya92 只是当前函数的上下文中会等待在这里,但是每个等待的时候,都有可能存在其他函数在执行,和回调一样是异步的,这和同步是有区别的
    est
        53
    est  
       2019-01-16 14:04:25 +08:00
    优雅和 优雅 是互斥的。
    est
        54
    est  
       2019-01-16 14:04:31 +08:00
    优雅和 nodejs 是互斥的。
    pkoukk
        55
    pkoukk  
       2019-01-16 14:09:33 +08:00
    主要是很多第三方库是照着 promise 和回调设计的,所以有些地方没办法用 async/await 解决。
    比如 sequelize 的 transcation
    wly19960911
        56
    wly19960911  
       2019-01-16 14:22:08 +08:00
    @reus #51 并发和 js 有什么关系嘛。并发是一个实现,js 只是工具,实际上异步实现并发性能还更好。如果是多线程的并发,还得玩多线程,相比异步反而麻烦了。
    janxin
        57
    janxin  
       2019-01-16 14:30:58 +08:00
    @tomoya92 乱不就是难看么...
    oyjw443523
        58
    oyjw443523  
       2019-01-16 14:38:12 +08:00
    想不用回调就去试下 go,得用协程
    yoshiyuki
        59
    yoshiyuki  
       2019-01-16 14:50:17 +08:00
    异步回调是为了实现更好的 IO 性能,如果不需要 IO 性能可以考虑改用 PHP、Python 等
    tomoya92
        60
    tomoya92  
    OP
       2019-01-16 14:56:46 +08:00
    @oyjw443523 #58 go 用过,写着很舒服,不过还是没有 nodejs 用的广,而且公司要用啥也不是我说的算的 :joy
    tomoya92
        61
    tomoya92  
    OP
       2019-01-16 14:57:36 +08:00
    @yoshiyuki #59 其实用 nodejs 还有一个好处,就是快,开发效率要比 java 不知道高多少 :joy
    yoshiyuki
        62
    yoshiyuki  
       2019-01-16 15:23:38 +08:00
    @tomoya92 PHP 开发更快
    TomVista
        63
    TomVista  
       2019-01-16 15:36:42 +08:00
    ```
    function some (callback){
    }

    function callback(callback2){
    }

    function callback2(callback3){
    }
    ...


    ```
    Ritr
        64
    Ritr  
       2019-01-16 16:44:50 +08:00
    js 本身就是异步的呀,所以我打算学个其他语言
    reus
        65
    reus  
       2019-01-16 17:19:33 +08:00
    @wly19960911 我用多线程从来就不用考虑这个帖子提出的问题
    justin2018
        66
    justin2018  
       2019-01-16 22:31:15 +08:00
    长时间不写 我总是会忘记~ 不知道为啥~~~
    tomoya92
        67
    tomoya92  
    OP
       2019-01-16 23:11:01 +08:00 via iPhone
    @justin2018 我经常用,都分不清,更别说长时间不用了😂
    libook
        68
    libook  
       2019-01-16 23:42:52 +08:00
    如果执意使用回调函数的思想来设计程序的话,用什么语言都会有这个问题。

    JS 是可以完全不用回调函数来设计数据流的,你都用 async await 了,为什么还要用回调函数来传递数据?可以拿出几个例子来,一定有更好的代码的组织方案的。
    libook
        69
    libook  
       2019-01-16 23:49:01 +08:00
    it ('waitBody', async function() {
    const code = await driver.sleep(500).wait('body', 30000).html();
    isPageError(code).should.be.false;
    })

    这块没必要再用 then 了,你都用 await 了,那就一定是等着这段代码执行完再执行下一句。
    lhx2008
        70
    lhx2008  
       2019-01-17 00:02:14 +08:00 via Android
    @tomoya92 说 python 快我还信了,普通 js 真的不行,npm 装个包都不知道要多久,一升级就各种瞎改,异步也不好写,异常链+正常链+多线程都难搞死了,维护别人代码都想砍人
    sagaxu
        71
    sagaxu  
       2019-01-17 00:10:28 +08:00 via Android
    @tomoya92 那是你不熟悉现代 Java
    busfool
        72
    busfool  
       2019-01-17 06:47:36 +08:00 via Android
    没有,js 异步无法优雅处理
    tomoya92
        73
    tomoya92  
    OP
       2019-01-17 09:20:00 +08:00
    @sagaxu #71 我使用 java 还停留在 8 的版本上
    zy445566
        74
    zy445566  
       2019-01-17 10:05:27 +08:00
    把所有的回调都封装成 Promise,然后返回给上面 await 就好了
    KuroNekoFan
        75
    KuroNekoFan  
       2019-01-17 10:31:20 +08:00
    关于 promiseFunc.then(some_stuff)的观感问题,你可以
    Promise.resolve()
    .then(stuff_1)
    .then(stuff_2)
    //...
    salamanderMH
        76
    salamanderMH  
       2019-01-17 10:47:10 +08:00
    promisify
    no1xsyzy
        77
    no1xsyzy  
       2019-01-17 11:31:52 +08:00
    @oyjw443523 #58 协程不就是 async/await 吗
    其实 NodeJS ( Python 的也是)的 async/await 就是把异步变成协程写法。
    ——
    @lhx2008 #70 yarn,请
    ——
    其实异步除了回调、Promise/Future、async/await (包括协程)还有一种就是信号式。
    信号式其实多线程下也能很好用,调用链清晰,程序本身就是个系统设计图。
    缺点就是状态不能自然保留和共享(必然手动维护 context ),并且对一本道的代码没特别大的影响。
    exonuclease
        78
    exonuclease  
       2019-01-17 14:36:11 +08:00   ❤️ 1
    我搞了个 node 的 c++扩展 可以强制把异步调用同步执行 不过代价是性能
    hoyixi
        79
    hoyixi  
       2019-01-20 13:51:40 +08:00
    就你给的代码,你不想写 then 回调,
    直接:
    code = await。。。。
    if (判断 code )。。。

    这不就是同步写法吗,await 的优势就是能让你这么写啊,你又不用
    angsheng
        80
    angsheng  
       2019-12-28 23:03:12 +08:00   ❤️ 1
    我个人就很喜欢写回调套回调,当发现回调卡死了写不下去时候比如 fs.statu 与 fs.read 的配套操作的时候,问题出在了整个处理流程的设计,反过头重新设计就是了。
    当然我理解“回调地狱”,,,但我对 Promise、async/await 就感到特别头疼,总是想不起来这个对象怎么用,真希望能尊重下像我这种习惯写回调而不习惯新语法的。
    nodejs 就是 IO 类 API 是与主线程分开的另一个线程(其本身是不是队列结构实在是忘了),所以这里姑且认为至少有两个线程,代码顺序的主线程和 IO 类 API 所在那个线程。Promise 之类折腾来去无非是换了个写法而已(从程序员的层面来看)。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1410 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 17:30 · PVG 01:30 · LAX 09:30 · JFK 12:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.