Node 4.4.x 里面尝试async / await
发布于 14 天前 作者 ankyhe 344 次浏览 来自 分享

什么是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早日可以更好的支持异常的优化。

6 回复

推荐别用request-promise了,试试superagent, axios, got。都原生支持promise,比较简洁。request太臃肿,虽然promise过了但是里面其实还是async写的

@andyhu 谢谢,但这不是我这篇文章的重点。

@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'));


对了,为何不用 node 6.2 而用 node 4.4?

回到顶部