什么是async/await
async/await 的灵感来自于C#,是ES7的一部分。async/await是一种方法,可以让我们不用写callback,想同步方式那样使用函数。下面是一个例子:
import 'request' from request;
function requestPromise(config, acceptStatusCode = (statusCode) => statusCode === 200) {
let promiseCallbackAF;
return new Promise(promiseCallbackAF = (fullfil, reject) => {
let responseCallbackAF;
request(config, responseCallbackAF = (err, response) => {
if (err) {
return reject(new RequestError(err));
}
if (!acceptStatusCode(response.statusCode)) {
return reject(new ResponseStatusError('Response status code is not accepted.', response));
}
return fulfill(response);
});
});
}
async function main() {
const response = await requestPromise({url:'http://www.baidu.com'});
console.log('response is %s', response.body.substr(0, 100));
return 0;
}
main();
怎样在Node 4.4.x里面使用async/await
现在的Node 4.4.x (甚至是Node 6.x) 都不支持async/await, 请查阅:http://kangax.github.io/compat-table/esnext/ 。但是我们可以通过Babel来支持。
$ npm install babel-plugin-transform-async-to-module-method --save-dev
$ npm install babel-preset-es2015-node4 --save-dev
$ npm install babel-preset-stage-3 --save-dev
$ npm install bluebird --save
并且使用下面的.babelrc文件
{
"presets": ["es2015-node4", "stage-3"],
"plugins": [["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
}], "transform-es2015-parameters"]
}
错误处理
async function foo(url) {
return await requestPromise({url});
}
async function main() {
const response = await foo('http://www.macx.cn/no-page');
console.log('response is %s', response.substr(0, 100));
}
之后运行程序可以发现程序会打印出所有的callstack(这个非常非常有用)
Unhandled rejection Error: Response status code 404 is not accepted.
at ResponseStatusError (test.js:16:5)
at Request.responseCallbackAF [as _callback] (test.js:37:23)
at Request.self.callback (/Users/ankyhe/work/tools/health-test/node_modules/request/request.js:200:22)
at emitTwo (events.js:87:13)
at Request.emit (events.js:172:7)
at Request.<anonymous> (/Users/ankyhe/work/tools/health-test/node_modules/request/request.js:1067:10)
at emitOne (events.js:82:20)
at Request.emit (events.js:169:7)
at IncomingMessage.<anonymous> (/Users/ankyhe/work/tools/health-test/node_modules/request/request.js:988:12)
at emitNone (events.js:72:20)
at IncomingMessage.emit (events.js:166:7)
at endReadableNT (_stream_readable.js:913:12)
at nextTickCallbackWith2Args (node.js:442:9)
at process._tickDomainCallback (node.js:397:17)
From previous event:
at foo (/Users/ankyhe/work/tools/health-test/test.js:10:16)
at test.js:49:26
at [object Generator].next (native)
From previous event:
at main (/Users/ankyhe/work/tools/health-test/test.js:20:16)
at Object.<anonymous> (test.js:53:1)
at Module._compile (module.js:409:26)
at loader (/Users/ankyhe/work/tools/health-test/node_modules/babel-cli/node_modules/babel-register/lib/node.js:158:5)
at Object.require.extensions.(anonymous function) [as .js] (/Users/ankyhe/work/tools/health-test/node_modules/babel-cli/node_modules/babel-register/lib/node.js:168:7)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
at /Users/ankyhe/work/tools/health-test/node_modules/babel-cli/lib/_babel-node.js:160:24
at Object.<anonymous> (/Users/ankyhe/work/tools/health-test/node_modules/babel-cli/lib/_babel-node.js:161:7)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
at startup (node.js:139:18)
at node.js:968:3
最后的问题
大家可以看到上面这种处理方式非常非常像“传统” Java / Python/ Ruby 的编码方式,但是,请务必注意异常处理的方式。不要在v8里面写这样的代码:
function foo() {
try {
// code 1
// code 2
// code 3
catch (err) {
// exception handler
}
}
这样的代码v8是不能优化的,比较好的方式是:
function bar() {
// code 1
// code 2
// code 3
}
function foo() {
try {
foo();
} catch (err) {
// exception handler
}
}
但是,其实很多都建议不要在node里面使用异常处理 所以我们只好选择另外一种方式:
function requestPromise(config, acceptStatusCode = (statusCode) => statusCode === 200) {
let promiseCallbackAF;
return new Promise(promiseCallbackAF = (fulfill) => {
let responseCallbackAF;
request(config, responseCallbackAF = (err, response) => {
if (err) {
return fulfill({err: new RequestError(err)});
}
if (!acceptStatusCode(response.statusCode)) {
return fulfill({err: new ResponseStatusError(`Response status code ${response.statusCode} is not accepted.`, response)});
}
return fulfill({response});
});
});
}
async function foo(url) {
return await requestPromise({url});
}
async function main() {
const {err, response} = await foo('http://www.macx.cn/no-page');
if (err) {
return console.log('Failed for request due to: %s\n%s', err, err.stack);
}
console.log('response is %s', response.substr(0, 100));
return 0;
}
这种方式其实就是同步化的callback (在node里面,默认的callback都是(err, data)这样的)。如果这个promise不是你自己的,而是别人生成的,我们也可以包装一下,
function makeNoRejectPromise(promise) {
return new Promise(fulfill) {
promise.then(function success(data) {
return fullfil({data});
}, function failure(err) {
return fulfill({err});
});
}
}
这种做法现阶段还不错,只要在自己的代码里面遵循这个方式就好了。但是对于整个社区的编码风格,还是用异常可能更好一些。所以希望v8早日可以更好的支持异常的优化。
推荐别用request-promise了,试试superagent, axios, got。都原生支持promise,比较简洁。request太臃肿,虽然promise过了但是里面其实还是async写的
@ankyhe 嗯,我最近尝试了很多种不同的http client,觉得还是superagent比较不错,如果喜欢极简风格的话可以试试got,axios差不多是功能比较全的类request的东西,很方便,但是具体有不少小bug。如果需要兼容浏览器前后端,node-fetch也挺好用,和浏览器自带的fetch api几乎一样,简洁也节约资源。
不过有个问题想请教下,async function,如何并发请求的时候控制concurrency?怎么能用bluebird之类的promise方法来直接使用async function?
自问自答了,想了个这个办法:
import sleep from 'then-sleep';
import Promise from 'bluebird';
const asyncToPromise = func => {
return (...args) => Promise.try(async () => await func.apply(this, args));
};
const sleepAsync = async (ms) => {
console.time(`sleep ${ms}ms`);
await sleep(ms);
console.timeEnd(`sleep ${ms}ms`);
};
console.time('total');
Promise.map([1000, 2000, 3000], (ms) => asyncToPromise(sleepAsync)(ms), { concurrency: 1 })
.then(() => console.timeEnd('total'));