一个解决异步编程种种问题的方案
发布于 10 年前 作者 iMumuMua 5344 次浏览 最后一次编辑是 8 年前 来自 分享

众所周知,在node.js经常要调用一些异步的方法,通过回调函数获取数据,然后必须在这个回调函数里进行下一步的处理。这样,很容易就落入“回调地狱”,即很可能写出层层嵌套的回调函数,这倒还不是什么大问题,通过一些编程技巧就可以解决。“回调地狱”真正可怕的是,不能保证这个回调函数会不会执行、会执行几次,这样就相当于把程序的控制权转移出去了,程序的安全性和可靠性大大降低。所以,Promise诞生了,解决了这几个棘手的问题。 然而,使用Promise还是会遇到一些问题的。最常见的一个问题是,想中断Promise链:

promiseA.then(function(data) {
    if (something) {
        // to break
    }
    else {
        return promiseB;
    }
}).then(function(data) {
    // doing something
}).then(null, console.log);

如果想中断,只能是抛出异常,但这是正常结束,不合逻辑;或是嵌套:

promiseA.then(function(data) {
    if (something) {
        // to break
    }
    else {
        return promiseB.then(function(data) {
            // doing something
        });
    }
}).then(null, console.log);

一个分支的情况下还好办,但如果再复杂些,就要嵌套多层了。

还有一个比较麻烦的就是代码组织的问题。很多时候,其实都希望异步获取到的数据和处理过程是看作一步的,但使用Promise就需要不间断地组成Promise链一直then下去,让一个过程变得很长。如果说把then中的回调函数抽出来写,那就更难维护了,因为会变成这样:

var thenA = function(dataA) {
    // doSomething
    // 处理完A的数据后,就必须莫名奇妙地返回一个下一步的Promise
    // 尽管这很可能会与dataA有联系,但还是会感觉很奇怪的
    // 这是因为这个函数做了两件关联不太事:处理数据,然后执行一个异步过程,等待下一步处理
    return promiseB;
};
promiseA.then(thenA).then(function(dataB) {}).then(null, console.log);

总之,在一个函数里似乎是不太方便同时写获取数据并处理的过程,要放到下一个then去处理,使得这些Promise难解难分,会出现一大段没法解耦的代码。

还有一个问题,就是复杂的流程控制。虽然有一些像Q这样的库提供了并发处理多个Promise的方法,让Promise做简单的顺序和并发流程不成问题,但是如果复杂一点,例如有一些条件分支,还有循环等,就得再临时创建一些Promise了,代码量就变大了。

为了解决上述问题,我尝试性地写了一个库:magic-task,在这里和大家分享,希望可以给大家带来方便。

11 回复

虽然现在是generator的时代了, 还是赞一下

if else 嵌套,这些编程最基本的东西,在nodejs里面搞得跟天书一样

@Pana 确实是,有了generator好多了,这是我在不使用generator的情况做的最后的挣扎~

@yakczh 异步回调好头疼~

我感觉还是generator治本,其他方法还是很容易就掉到地狱里

@iMumuMua @yakczh 你好,我花2年的功夫思考并设计了一门新的语言lix,它最终把代码编译成js,用nodejs运行,我觉得很好的解决了这个问题。这是我项目的github地址: https://github.com/lixinqi/lix。希望不会让你失望:-)

@lixinqi

sleep := [ms]->{
  cc call [brk]->{
    ms timeout []->{
      brk call
    }
  }
}

i := 0
while (i < 10) {
  20 sleep
  i print
  i = i + 1
}

'end' print

看来我说天书,一点都没说错

sas callback 18层毫无压力.

@yakczh 我再把它写成中文,这样更没节操一点:-)

暂停 := [ms]->{
  当前续点 执行 [继续]->{
	ms 毫秒超时后 []->{
	  继续 执行
	}
  }
}

毫秒 := [i]->{i}

i := 0
while (i < 10) {
  20 | 毫秒 | 暂停
  i 打印到终端
  i = i + 1
}

'end' 打印到终端

它真的能把异步的setTimeout在代码逻辑上变成同步的函数

不要在意语法细节 :-)

有人能看懂上面的代码吗?

回到顶部