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

发布于 1周前 作者 nodeper 来自 nodejs/Nestjs

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

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


81 回复

没有,这就叫异步,你可以改用 Python。


promise 不能 await?

async 都不能忍受的话 要不考虑学个正经的偏后端语言?

最好的办法就是不写 JS (滑鸡)

你这个写的感觉有问题。你再好好看看。

#6 感谢大佬指点,原来 nodejs 内置已经实现了一个 promise,并且有一定的规则,谢谢

这个和 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;
})();
})

#7 前端萌新,一直都是迷迷糊糊的用 nodejs,最近觉得非常有必要把这个弄清楚,所以才发帖求助的,还请见谅 :joy

#9 感谢指点,上面那段代码不是我写出来的,是 uirecorder 录制完成后自动生成的测试用例里的代码,我也觉得在回调上还用 async await 总觉得别扭 :joy

async 和 await 是把一切耗时的操作打上一个 await,然后整体下来看起来就像同步,而不是像你那样 promise 和 async 混搭

js 语言特色就是单线程异步,你要先理解 async 解决了什么问题

#12 await 不是等待的意思吗?加上这个关键字后,后面的代码要等待这行代码执行完才往下继续执行,不应该是这个意思吗?

#14 是的,然后你就没必要去不断的 写个 promise.then()去处理回调了,把 promise.then()的东西丢到一个方法里面,一个个 await 调用,实际上会比 promise.then()好看,而且逻辑上清晰。async 没有解决什么根本上的问题,只是一种新的写法让异步回调看起来像同步,把箭头函数里面的东西丢到一个封装方法里面,最后到达一个更直观的代码.

没看懂都 await 了,为什么还要 then。。讲道理是不会出现一个 then 的

这个例子本身写的不好,看面条代码脑子绕圈是正常反应,要先坚实自己,就不会被带偏

可以看 Promise 迷你书

试试这样
const code = await driver.sleep(500).wait(‘body’, 30000).html();
isPageError(code).should.be.false;

async 和 await 是目前最优雅的了

跟着网上的教程学呀,发现用上 await 就能同步了,就用上了

回调是 js 的精髓吧

回调是不可能避免的,只是封装成 promise,让.then 或者 await 调用而已
.then 和 await 只是为了让代码变得好看
比如
await cb1()
await cb2()
await cb3()
要比
f()
.then(cb1())
.then(cb2())
.then(cb3())
或者
f(cb3(cb2(cb1)))
好看一些

#22 我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题

那么,你可以去看看 fibjs
另外你可以用 promise 包装回调,然后 await
然而我觉得是你没明白 promise 和 async 是做了什么


我也是觉得 async await 这种就是让代码好看的,还是没有解决回调问题
----------------------
因为回调本来就不是问题啊,nodejs 的精髓你要把它“解决”掉,那为什么要用 nodejs 呢

还有一种方法是响应式编程,rxjs 或者 reactorjs,不过也只是看起来比较美好,await 那种还是好用一点

#24 我确实是糊里糊涂的

#26 嗯,谢谢,我觉得我还是像楼上说的那样,先把 promise async await 它们都是做什么的弄清楚比较好

ps:async await 可不只是好看一点这么简单。
async 给了你统一的错误流,以及可精确控制范围的 try catch。
没有 async 前,写复杂异步链的异常处理非常之痛苦……

Promise + async/await 已经比较优雅了

异步适用一次回调,回调使用多次调用

用了 await 还用 then ??

#6 大佬,再打扰一下,看你给的链接里的介绍,fs.stat 方法传进 util.promisify(fs.stat) 里当参数后,下面就可以直接使用 async/await 来同步执行拿返回值数据了,当然它们有规则,就是 fs.stat 方法的回调第一个参数要是异常

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

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

js<br>function timeout(ms) {<br> return new Promise((resolve, reject) =&gt; {<br> setTimeout(resolve, ms, 'done');<br> });<br>}<br><br>timeout(100).then((value) =&gt; {<br> console.log(value);<br>});<br>

js<br>const timeout = util.promisify(setTimeout);<br>timeout(100).then((value) =&gt; {<br> console.log(value);<br>});<br>

谢谢!

js<br><br>function timeout(ms) {<br> return new Promise((resolve, reject) =&gt; {<br> setTimeout(resolve, ms);<br> });<br>}<br><br>await timeout(1000);<br><br>

这就是 nodejs 的特点…如果没有异步,单线程模型就无法支持多用户访问了。
某些模块的 api 还是支持同步调用的,比如 fs.readFileSync…写起来方便但是就阻塞了

代码清楚就行了,async/await 只是语法糖,不用太敏感

await 之后的代码逻辑上已经是被 await 的 promise 的 then 的 callback,只是看起来代码是“连续”的

"任何一个被包装成了 Promise 的函数都可以使用 async/await 来同步执行拿返回值的数据?"
await 期待的就是一个 promise
(如果 await 后面跟的是一个普通的 function, 也不会报错, 只是 await 变得毫无意义了)

#37 哦哦,明白了,那我理解的就是对的了,谢谢!

顶楼的代码是不是也可以写成

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

? 这样会清楚点吗?

这段代码用回调写写看,就知道 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;
}
}

await 一个不 thenable 的表达式也是有作用的,await 后的代码一定在栈清空后才被执行 (和 promise 的 then 同样保证)

考虑:

<br>async function foo() {<br> console.log(1);<br> await 0;<br> console.log(3);<br>}<br><br>foo();<br>console.log(2);<br>

是不是有"意义"这个自己判断吧

回调最大的问题难道不是很难看么

回调的另一个问题是这个语法不自带 “可组合性”,表现为层数多起来就很难连异常处理一起写对

而 await 时 promise fulfill 天然映射到表达式求值,promise reject 天然映射到求值中 throw,没被 catch 的 reject 会自动向上传递到一个无法忽略的地方

#42 还真不是,如果业务复杂,回调多了,会很乱的

回调函数的循环控制嘛,这个在没有 await 的时候又不是没有

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

这段程序要是没理解错的话,是需要依次获取数据直到得到正确结果
可以构造一个对象,包含当前执行到的位置(i),最多可以达到的位置(10),函数 f(this.i),每次 f 执行之后 this.i++或者 cb(ret.data)

不要把 async/await 当成同步。。它们只是看起来同步,实际上还是异步,异步逻辑不会消失,只会看起来变得优雅。把 async/await 当成同步那会给自己挖坑的。。

手动维护一个 context 心智负担太重,这只是一个简单的例子,循环可以有多层,调用链路可以有十几层,实际逻辑可能复杂的多,相当于人肉维护一个状态机。同步写法,思路顺畅许多,也更不容易出错。

不仅是 js,各种语言都在往弃回调,引入同步写法。

#46 也就是说用了 async/await 代码会在 await 那地方等着执行完对吧,至于被调用方法执行是同步执行还是异步执行的,不是 async/await 管的了,是这个意思不?

嗯哼,好像理念并不冲突啊,谁不喜欢用同步写法去调用异步函数呢

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

当其他库没法直接用,或者为了兼容性还在使用回调的时候,自己封装一下就显得很必要

不要用 js 不就行了,找个对并发支持更好的不就行了

只是当前函数的上下文中会等待在这里,但是每个等待的时候,都有可能存在其他函数在执行,和回调一样是异步的,这和同步是有区别的

优雅和 优雅 是互斥的。

优雅和 nodejs 是互斥的。

主要是很多第三方库是照着 promise 和回调设计的,所以有些地方没办法用 async/await 解决。
比如 sequelize 的 transcation

#51 并发和 js 有什么关系嘛。并发是一个实现,js 只是工具,实际上异步实现并发性能还更好。如果是多线程的并发,还得玩多线程,相比异步反而麻烦了。

乱不就是难看么…

想不用回调就去试下 go,得用协程

异步回调是为了实现更好的 IO 性能,如果不需要 IO 性能可以考虑改用 PHP、Python 等

#58 go 用过,写着很舒服,不过还是没有 nodejs 用的广,而且公司要用啥也不是我说的算的 :joy

#59 其实用 nodejs 还有一个好处,就是快,开发效率要比 java 不知道高多少 :joy

PHP 开发更快

<br>function some (callback){<br>}<br><br>function callback(callback2){<br>}<br><br>function callback2(callback3){<br>}<br>...<br><br><br>

js 本身就是异步的呀,所以我打算学个其他语言

我用多线程从来就不用考虑这个帖子提出的问题

长时间不写 我总是会忘记~ 不知道为啥~~~

我经常用,都分不清,更别说长时间不用了😂

如果执意使用回调函数的思想来设计程序的话,用什么语言都会有这个问题。

JS 是可以完全不用回调函数来设计数据流的,你都用 async await 了,为什么还要用回调函数来传递数据?可以拿出几个例子来,一定有更好的代码的组织方案的。

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

这块没必要再用 then 了,你都用 await 了,那就一定是等着这段代码执行完再执行下一句。

说 python 快我还信了,普通 js 真的不行,npm 装个包都不知道要多久,一升级就各种瞎改,异步也不好写,异常链+正常链+多线程都难搞死了,维护别人代码都想砍人

那是你不熟悉现代 Java

没有,js 异步无法优雅处理

#71 我使用 java 还停留在 8 的版本上

把所有的回调都封装成 Promise,然后返回给上面 await 就好了

关于 promiseFunc.then(some_stuff)的观感问题,你可以
Promise.resolve()
.then(stuff_1)
.then(stuff_2)
//…

#58 协程不就是 async/await 吗
其实 NodeJS ( Python 的也是)的 async/await 就是把异步变成协程写法。
——
#70 yarn,请
——
其实异步除了回调、Promise/Future、async/await (包括协程)还有一种就是信号式。
信号式其实多线程下也能很好用,调用链清晰,程序本身就是个系统设计图。
缺点就是状态不能自然保留和共享(必然手动维护 context ),并且对一本道的代码没特别大的影响。

我搞了个 node 的 c++扩展 可以强制把异步调用同步执行 不过代价是性能

就你给的代码,你不想写 then 回调,
直接:
code = await。。。。
if (判断 code )。。。

这不就是同步写法吗,await 的优势就是能让你这么写啊,你又不用

我个人就很喜欢写回调套回调,当发现回调卡死了写不下去时候比如 fs.statu 与 fs.read 的配套操作的时候,问题出在了整个处理流程的设计,反过头重新设计就是了。
当然我理解“回调地狱”,,,但我对 Promise、async/await 就感到特别头疼,总是想不起来这个对象怎么用,真希望能尊重下像我这种习惯写回调而不习惯新语法的。
nodejs 就是 IO 类 API 是与主线程分开的另一个线程(其本身是不是队列结构实在是忘了),所以这里姑且认为至少有两个线程,代码顺序的主线程和 IO 类 API 所在那个线程。Promise 之类折腾来去无非是换了个写法而已(从程序员的层面来看)。

在 Node.js 中,确实存在多种优雅的方法来避免回调地狱(callback hell),使代码更加清晰和易于维护。以下是几种常见的方法:

  1. Promises: Promises 提供了一种更优雅的方式来处理异步操作。它们允许你使用 .then().catch() 方法来链式调用,从而避免深度嵌套的回调。

    function asyncOperation1() {
        return new Promise((resolve, reject) => {
            // 模拟异步操作
            setTimeout(() => resolve('Result 1'), 1000);
        });
    }
    
    asyncOperation1()
        .then(result1 => {
            console.log(result1);
            return asyncOperation2(); // 另一个异步操作
        })
        .then(result2 => {
            console.log(result2);
        })
        .catch(error => {
            console.error(error);
        });
    
  2. Async/Await: Async/Await 是基于 Promises 的语法糖,使异步代码看起来更像是同步代码,进一步提升了可读性。

    async function fetchData() {
        try {
            const result1 = await asyncOperation1();
            console.log(result1);
            const result2 = await asyncOperation2();
            console.log(result2);
        } catch (error) {
            console.error(error);
        }
    }
    
    fetchData();
    

通过使用 Promises 和 Async/Await,你可以显著简化 Node.js 中的异步代码,使其更加优雅和易于管理。

回到顶部