首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Node.js
Express
PPA for Ubuntu
ppa:chris-lea/node.js
V2EX  ›  Node.js

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

  •  3
     
  •   tomoya92 · 326 天前 · 5384 次点击
    这是一个创建于 326 天前的主题,其中的信息可能已经有所发展或是发生改变。

    虽然现在 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 的朋友都有过这样的疑惑 😂

    79 回复  |  直到 2019-01-20 13:51:40 +08:00
        1
    oott123   326 天前 via Android   ♥ 1
    没有,这就叫异步,你可以改用 Python。
        2
    sagaxu   326 天前 via Android
    promise 不能 await?
        3
    lekai63   326 天前 via iPhone
    async 都不能忍受的话 要不考虑学个正经的偏后端语言?
        4
    misaka19000   326 天前
    最好的办法就是不写 JS (滑鸡)
        5
    avastms   326 天前 via Android
    promisify
        6
    tcdw   326 天前 via Android
    > 很多函数的调用的写法还是会用到回调

    https://nodejs.org/api/util.html#util_util_promisify_original
        7
    Hanggi   326 天前
    你这个写的感觉有问题。你再好好看看。
        8
    tomoya92   326 天前
    @tcdw #6 感谢大佬指点,原来 nodejs 内置已经实现了一个 promise,并且有一定的规则,谢谢
        9
    wly19960911   326 天前
    这个和 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;
    })();
    })
        10
    tomoya92   326 天前
    @Hanggi #7 前端萌新,一直都是迷迷糊糊的用 nodejs,最近觉得非常有必要把这个弄清楚,所以才发帖求助的,还请见谅 :joy
        11
    tomoya92   326 天前
    @wly19960911 #9 感谢指点,上面那段代码不是我写出来的,是 uirecorder 录制完成后自动生成的测试用例里的代码,我也觉得在回调上还用 async await 总觉得别扭 :joy
        12
    wly19960911   326 天前
    async 和 await 是把一切耗时的操作打上一个 await,然后整体下来看起来就像同步,而不是像你那样 promise 和 async 混搭
        13
    Pastsong   326 天前 via Android
    js 语言特色就是单线程异步,你要先理解 async 解决了什么问题
        14
    tomoya92   326 天前
    @wly19960911 #12 await 不是等待的意思吗?加上这个关键字后,后面的代码要等待这行代码执行完才往下继续执行,不应该是这个意思吗?
        15
    wly19960911   326 天前   ♥ 1
    @tomoya92 #14 是的,然后你就没必要去不断的 写个 promise.then()去处理回调了,把 promise.then()的东西丢到一个方法里面,一个个 await 调用,实际上会比 promise.then()好看,而且逻辑上清晰。async 没有解决什么根本上的问题,只是一种新的写法让异步回调看起来像同步,把箭头函数里面的东西丢到一个封装方法里面,最后到达一个更直观的代码.
        16
    Sparetire   326 天前 via Android
    没看懂都 await 了,为什么还要 then。。讲道理是不会出现一个 then 的
        17
    autoxbc   325 天前   ♥ 1
    这个例子本身写的不好,看面条代码脑子绕圈是正常反应,要先坚实自己,就不会被带偏

    可以看 Promise 迷你书
        18
    omnip   325 天前
    试试这样
    const code = await driver.sleep(500).wait('body', 30000).html();
    isPageError(code).should.be.false;
        19
    edward8628   325 天前
    async 和 await 是目前最优雅的了
        20
    tomoya92   325 天前 via iPhone
    @Sparetire 跟着网上的教程学呀,发现用上 await 就能同步了,就用上了
        21
    motai   325 天前 via iPhone
    回调是 js 的精髓吧
        22
    lzvezr   325 天前 via Android
    回调是不可能避免的,只是封装成 promise,让.then 或者 await 调用而已
    .then 和 await 只是为了让代码变得好看
    比如
    await cb1()
    await cb2()
    await cb3()
    要比
    f()
    .then(cb1())
    .then(cb2())
    .then(cb3())
    或者
    f(cb3(cb2(cb1)))
    好看一些
        23
    tomoya92   325 天前
    @lzvezr #22 我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题
        24
    des   325 天前 via Android
    那么,你可以去看看 fibjs
    另外你可以用 promise 包装回调,然后 await
    然而我觉得是你没明白 promise 和 async 是做了什么
        25
    shintendo   325 天前   ♥ 1
    @tomoya92
    我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题
    ----------------------
    因为回调本来就不是问题啊,nodejs 的精髓你要把它“解决”掉,那为什么要用 nodejs 呢
        26
    lhx2008   325 天前 via Android
    还有一种方法是响应式编程,rxjs 或者 reactorjs,不过也只是看起来比较美好,await 那种还是好用一点
        27
    tomoya92   325 天前
    @des #24 我确实是糊里糊涂的
        28
    tomoya92   325 天前
    @lhx2008 #26 嗯,谢谢,我觉得我还是像楼上说的那样,先把 promise async await 它们都是做什么的弄清楚比较好
        29
    woodensail   325 天前   ♥ 1
    ps:async await 可不只是好看一点这么简单。
    async 给了你统一的错误流,以及可精确控制范围的 try catch。
    没有 async 前,写复杂异步链的异常处理非常之痛苦……
        30
    learnshare   325 天前
    Promise + async/await 已经比较优雅了
        31
    qiushijie   325 天前 via Android
    异步适用一次回调,回调使用多次调用
        32
    wunonglin   325 天前
    用了 await 还用 then ??
        33
    tomoya92   325 天前
    @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);
    });
    ```

    谢谢!
        34
    cuberlzy   325 天前
    ``` js

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

    await timeout(1000);

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

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

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

    ? 这样会清楚点吗?
        40
    sagaxu   325 天前 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;
    }
    }
        41
    momocraft   325 天前   ♥ 2
    await 一个不 thenable 的表达式也是有作用的,await 后的代码一定在栈清空后才被执行 (和 promise 的 then 同样保证)

    考虑:

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

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

    是不是有"意义"这个自己判断吧
        42
    janxin   325 天前
    @tomoya92 回调最大的问题难道不是很难看么
        43
    momocraft   325 天前   ♥ 1
    回调的另一个问题是这个语法不自带 "可组合性",表现为层数多起来就很难连异常处理一起写对

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

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

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

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

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

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

    function callback(callback2){
    }

    function callback2(callback3){
    }
    ...


    ```
        64
    Ritr   325 天前
    js 本身就是异步的呀,所以我打算学个其他语言
        65
    reus   325 天前
    @wly19960911 我用多线程从来就不用考虑这个帖子提出的问题
        66
    justin2018   325 天前
    长时间不写 我总是会忘记~ 不知道为啥~~~
        67
    tomoya92   325 天前 via iPhone
    @justin2018 我经常用,都分不清,更别说长时间不用了😂
        68
    libook   325 天前
    如果执意使用回调函数的思想来设计程序的话,用什么语言都会有这个问题。

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

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

    这不就是同步写法吗,await 的优势就是能让你这么写啊,你又不用
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1026 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 33ms · UTC 18:54 · PVG 02:54 · LAX 10:54 · JFK 13:54
    ♥ Do have faith in what you're doing.