JavaScript代码同步化
我最近一直在写js。在这期间,我在群里提的最多的问题就是
函数a在函数b执行之前就执行了,我应该怎么办?
我有一个用python写的网易云音乐的下载器,我最近在试图把它用nodejs重写一遍。
它的逻辑非常简单。但是我写了一半就写不下去了,因为一层一层的回调嵌套,代码已经成了>
形。
js一个重大的特点就是异步非阻塞,但是在一些情况下,下一步的操作需要依赖上一步的执行结果。这样就会有回调中再回调的情况出现。 当业务逻辑一复杂,回调的嵌套越来越多,可读性就会变差,维护起来也会很困难,这就是回调地狱。
node有很多第三方的模块用来将异步调用同步化,来解决这个问题。
回调函数
request.get( 'http://www.google.com/', function (error,response,body){ console.log(JSON.response.body); });
回调函数简单、容易理解,但是一层层嵌套下来,就形成了回调地狱,不利于代码的阅读。各个部分之间高度耦合,很难维护。
Promise
var promise = new Promise(function(resolve,reject){ request.get( 'http://www.google.com/', function (error,response,body){ if(error) reject(error); resolve(JSON.response.body); } }); promise.then(function(data){ console.log(data); });
用resolve和reject抛出返回值,将回调链式化。一方面使代码更优雅,另一方面也使回调更可控。
async/await
function getList() { return new Promise(function(resolve, reject) { request.get( 'http://www.example.cn/get_list', function(error, response, body) { resolve(JSON.parse(body)); } ) }); } async function main() { try { let list = await getList(); console.log(list); } catch (err) { console.log(err); } } main()
async/await本质上是基于generator和promise的语法糖。async
表示这个函数当中存在阻塞的操作,await
则表示这个函数会阻塞后面的代码。await
函数返回的结果是一个promise对象,由于await
函数的结果有可能是reject
,因此建议用try…catch包裹await
的函数。
使用上述模块,一方面为了消除回调地狱,另一方面用于真正需要同步的操作。但是不要滥用这些同步化模块。JS作为异步的语言,回调是很自然的模式。尽快转变思维,避免习惯性地用同步的思维去思考js。不然就会像用面向过程的思想来写c++一样。
很多时候,如果仅仅为了避免回调地狱,其实不需要async,generator或promise,只要对代码进行简单的封装即可。就能管理好相当多的回调场景。
不好的写法:
orm.connect("mysql://username:password@host/database", function(err, db) { if (err) throw err; var Person = db.define("person", SCHEMA); db.sync(function(err) { if (err) throw err; Person.create({ id: 1, name: "John", surname: "Doe", age: 27 }, function(err) { if (err) throw err; Person.find({ surname: "Doe" }, function(err, people) { if (err) throw err; console.log("People found: %d", people.length); console.log("First person: %s, age %d", people[0].fullName(), people[0].age); people[0].age = 16; people[0].save(function(err) {}); }); }); }); })
好的写法:
var db = orm.connect("creds", function (success, db) { if (!success) { console.log("Could not connect to database!"); return; } var Person = db.define("person", SCHEMA); }); db.xxxxx();
使用babel支持es7
如果想使用es7的新特性(比如async),但是当前环境还不支持。
可以使用bable将较新标准的代码转换为当前环境可以执行的代码
安装babel
npm install -g babel-cli
配置babel
向package.json
添加下述依赖,并安装
"dependencies": { "babel-plugin-syntax-async-functions": "^6.1.4", "babel-plugin-transform-regenerator": "^6.1.4", "babel-polyfill": "^6.1.4", "babel-preset-es2015": "^6.1.4", "request": "^2.65.0" }
npm install
创建.babelrc
,添加下述内容
{ "presets": ["es2015"], "plugins": ["syntax-async-functions","transform-regenerator"] }
转码并运行
向js代码中添加下述内容
require("babel-polyfill");
运行
babel origin.js -o dest.js && node dest.js
其中origin.js
为转码前的es7的代码,dest.js
为转码后的可以在当前环境下运行的代码。