为了解决这个阻塞问题,JavaScript严重依赖于回调,这是在长时间运行的进程(IO,定时器等)完成后运行的函数,因此允许代码执行经过长时间运行的任务。

downloadFile('example.com/weather.json', function(err, data) {
console.log('Got weather data:', data);
});

但是,问题来了,回调地狱

虽然回调的概念在理论上是巨大的,但它可能导致一些真正令人困惑和难以阅读的代码。 想象一下,如果你需要在回调后进行回调:

getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
...
});
});
});
});
});

你可以看到,这真的是一发不可收拾。 抛出一些if语句,for循环,函数调用或注释,你会有一些非常难读的代码。 初学者特别是这个的受害者,不理解如何避免这个“金字塔的厄运”。

这种层层嵌套的代码给开发带来了很多问题,主要体现在:

1.代码可能性变差
2.调试困难
3.出现异常后难以排查

解决方案

design around it

因此,许多程序员都陷入了回调地狱,因为这个(糟糕的设计)。 他们并没有真正考虑他们的代码结构提前,没有意识到他们的代码有多糟糕,意识到已经太晚了。 和你写的任何代码一样,你应该停下来思考可以做什么,使它在编写它之前或之后更简单,更可读。 这里有几个提示,你可以用来避免回调地狱(或至少管理它)。

Give your functions names

当读取代码(特别是乱码,无组织的代码)时,它很容易失去逻辑流,甚至语法,当小空间拥塞这么多嵌套回调。 帮助打击这一点的一个方法是命名你的函数,所以你需要做的是看一下名字,你会有一个更好的主意,它做什么。 它也给你的眼睛一个语法参考点。

考虑下面的代码:

var fs = require('fs');

var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function(err, txt) {
if (err) return console.log(err); txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});

看看这可能需要几秒钟来实现每个回调的作用和它开始的地方。 向函数中添加一些额外的信息(名称)会对可读性产生重大影响,尤其是在回调中有多个级别时:

var fs = require('fs');

var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function appendText(err, txt) {
if (err) return console.log(err); txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function notifyUser(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});

现在只是快速浏览会告诉你第一个函数附加一些文本,而第二个函数通知用户的变化。

Declare your functions beforehand

减少代码杂乱的最好方法之一是保持更好的代码分离。 如果你事先声明一个回调函数并稍后调用它,你将避免深层嵌套的结构,这使得回调很难使用。

So you could go from this...

var fs = require('fs');

var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function(err, txt) {
if (err) return console.log(err); txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});

...to this:

var fs = require('fs');

function notifyUser(err) {
if(err) return console.log(err);
console.log('Appended text!');
}; function appendText(err, txt) {
if (err) return console.log(err); txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, notifyUser);
} var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', appendText);

虽然这可以是一个很好的方式来帮助缓解问题,但它并不能完全解决问题。 当读取以这种方式编写的代码时,如果你不记得每个函数的确切位置,那么你必须回去查看每个函数,以回溯逻辑流程,这可能需要时间。

Use modules

在几乎每种编程语言中,降低复杂性的最好方法之一是模块化。 JavaScript也不例外。 每当你编写代码时,花一些时间来回顾一下你是否经常遇到一个常见的模式。

你在不同的地方多次写相同的代码吗? 你的代码的不同部分是否遵循一个共同的主题? 如果是这样,你有机会清理东西,抽象和重用代码。

有数千个模块,你可以看看供参考,但这里有几个要考虑。 它们处理常见的,但非常具体的任务,否则会扰乱你的代码并降低可读性:Pluralize,csv,qs,clone。

Here is a new file called formuploader.js that contains our two functions from before:

module.exports.submit = formSubmit

function formSubmit (submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, postResponse)
} function postResponse (err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
}

Now that we have formuploader.js (and it is loaded in the page as a script tag after being browserified) we just need to require it and use it! Here is how our application specific code looks now:

var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit

Async.js

幸运的是,像Async.js这样的库存在尝试和遏制这个问题。 Async在你的代码之上添加了一层函数,但可以通过避免回调嵌套大大降低复杂性。

许多辅助方法存在于Async中,可以在不同的情况下使用,例如系列,并行,瀑布等。每个函数都有一个特定的用例,所以花一些时间来了解哪个在哪些情况下会有帮助。

与Async一样好,像什么,它不完美。 它很容易结合series, parallel,waterfall, forever, etc,在这一点你回到你开始与凌乱的代码。 注意不要过早优化。 只是因为一些异步任务可以并行运行并不总是意味着他们应该。 实际上,由于Node只有单线程,因此使用Async时并行运行任务的性能增益很少甚至没有。

上面的代码可以使用Async的瀑布简化:(前一个函数的回调会作为后一个函数的参数,如果有任何任务通过一个错误的回调,下一个函数不执行)

var fs = require('fs');
var async = require('async'); var myFile = '/tmp/test'; async.waterfall([
function(callback) {
fs.readFile(myFile, 'utf8', txt);
},
function(txt, callback) {
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, callback);
}
], function (err, result) {
if(err) return console.log(err);
console.log('Appended text!');
});

Promises

虽然Promises可以花费一些时间来掌握,但在我看来,它们是您可以在JavaScript中学习的更重要的概念之一。 它不仅大大减少了代码行数,而且使代码的逻辑流程更容易遵循。

这里是一个使用非常快,非常受欢迎的Promise库,Bluebird的例子:

var Promise = require('bluebird');
var fs = require('fs');
Promise.promisifyAll(fs); var myFile = '/tmp/test';
fs.readFileAsync(myFile, 'utf8').then(function(txt) {
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt);
}).then(function() {
console.log('Appended text!');
}).catch(function(err) {
console.log(err);
});

请注意,这个解决方案不仅比以前的解决方案更短,而且更容易阅读(尽管,诚然,Promise风格的代码可能需要一些习惯)。 花时间学习和理解承诺,这将是值得你的时间。 但是,Promise绝对不是解决我们在异步编程中的所有问题,所以不要假设通过使用它们,你会有一个快速,干净,无bug的应用程序。 关键是知道什么时候对你有用。

一些Promise库你应该检查是Q,bluebird,或内置Promises如果你使用ES6的话。

Async/Await

注意:这是一个ES7功能,目前不支持Node或io.js。 但是,你现在可以使用它像Babel一样的转换器。

清除代码的另一个选项是我最近喜欢的(当它有更广泛的支持时),它使用异步函数。 这将允许你编写看起来更像同步代码,但仍然是异步的代码。

An example:

async function getUser(id) {
if (id) {
return await db.user.byId(id);
} else {
throw 'Invalid ID!';
}
} try {
let user = await getUser(123);
} catch(err) {
console.error(err);
}

The db.user.byId(id) call returns a Promise, which we'd normally have to use with .then(), but with await we can return the resolved value directly.

Notice that the function containing the await call is prefixed with async, which tells us that it contains asynchronous code and must also be called with await.

Another big advantage to this method is we can now use try/catchfor, and while with our asynchronous functions, which is much more intuitive than chaining promises together.

Aside from using transpilers like Babel and Traceur, you can also get functionality like this in Node with the asyncawait package.

避免这样的常见问题,如回调地狱不容易,所以不要期待立即结束你的挫折。 我们都陷入了它。 只是尝试减慢,花一些时间来思考你的代码的结构。 像任何事情,实践使完美。

避免Node.js中回调地狱的更多相关文章

  1. node.js 中回调函数callback(转载),说的很清楚,看一遍就理解了

    最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs.express 的代码就会看得一塌糊涂.比如: 复制代码 代码如下: app.use(fu ...

  2. node.js中的回调

    同步和阻塞:这两个术语可以互换使用,指的是代码的执行会在函数返回之前停止.如果某个操作阻塞,那么脚本就无法继续,这意味着必须等待. 异步和非阻塞:这两个术语可以互换使用,指的是基于回调的.允许脚本并行 ...

  3. 初步揭秘node.js中的事件

    当你学习node.js的时候,Events是一个非常重要的需要理解的事情.非常多的Node对象触发事件,你能在文档API中找到很多例子.但是关于如何写自己的事件和监听,你可能还不太清楚.如果你不了解, ...

  4. Node.js权威指南 (10) - Node.js中的错误处理与断言处理

    10.1 使用domain模块处理错误 / 272 10.1.1 domain模块概述 / 272 10.1.2 创建并使用Domain对象 / 274 10.1.3 隐式绑定与显式绑定 / 276 ...

  5. node.js中process进程的概念和child_process子进程模块的使用

    进程,你可以把它理解成一个正在运行的程序.node.js中每个应用程序都是进程类的实例对象. node.js中有一个 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息. ...

  6. node.js中通过dgram数据报模块创建UDP服务器和客户端

    node.js中 dgram 模块提供了udp数据包的socket实现,可以方便的创建udp服务器和客户端. 一.创建UDP服务器和客户端 服务端: const dgram = require('dg ...

  7. node.js中net网络模块TCP服务端与客户端的使用

    node.js中net模块为我们提供了TCP服务器和客户端通信的各种接口. 一.创建服务器并监听端口 const net = require('net'); //创建一个tcp服务 //参数一表示创建 ...

  8. node.js中stream流中可读流和可写流的使用

    node.js中的流 stream 是处理流式数据的抽象接口.node.js 提供了很多流对象,像http中的request和response,和 process.stdout 都是流的实例. 流可以 ...

  9. node.js中fs文件系统模块的使用

    node.js中为我们提供了fs文件系统模块,实现对文件或目录的创建,修改和删除等操作. fs模块中,所有的方法分为同步和异步两种实现. 有 sync 后缀的方法为同步方法,没有 sync 后缀的方法 ...

随机推荐

  1. 转 Android:sp与dp(densityDpi与scaledDensity)

    一般在布局上设置控件大小维度的单位采用dp,而定义字体大小的单位采用sp. dp是dip,density independent pixel,即密度无关的像素单位,说白了,就是这个维度相对于不同屏幕的 ...

  2. centos 7.1搭建docker本地私有仓库返回500错误

    之前有一篇写到在ubuntu14.04系统上安装私有仓库,遇到了两个问题,本次在centos7上遇到了另外一个问题. 安装完仓库并运行registry镜像之后发现push和pull操作都会返回一个50 ...

  3. codeforces 665B Shopping

    暴力 #include<cstdio> #include<cstring> #include<cmath> #include<vector> #incl ...

  4. css3的apprearance属性(转)

    appearance使用方法: .elmClass{ -webkit-appearance: value; -moz-appearance: value; appearance: value; } 接 ...

  5. 用JAVA捕获屏幕、屏幕录像、播放

    http://blog.csdn.net/njchenyi/article/details/447554 用JAVA捕获屏幕.屏幕录像.播放 标签: javaexceptionimageimportn ...

  6. HDU1251-统计难题(字典树)

    统计难题 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131070/65535 K (Java/Others)Total Submi ...

  7. springMVC 静态文件 访问

    1. 新建web project 2. 导入所需jar包 3. 更改web.xml <?xml version="1.0" encoding="UTF-8" ...

  8. android usb挂载分析---MountService启动

    android usb挂载分析---MountService启动 分类: android框架 u盘挂载2012-03-27 23:00 11799人阅读 评论(4) 收藏 举报 androidsock ...

  9. CSS——宽高问题大汇总

    1.宽高继承 他们是要属性的,并不是直接就能继承,inherit. 2.浮动的盒子不要给宽,宽度由内容来决定

  10. Nginx 虚拟主机下支持Pathinfo并隐藏入口文件的完整配置

    server { listen 80; server_name zuqiu.com; # 设置你的域名 index index.html index.htm index.php; root D:/wn ...