编写异步程序的最高境界就是它看起来是同步的。

Async Call

以下枚举最为经典的用法:

  • series(tasks, [callback]) 串行执行
    • waterfall(tasks, [callback]) 带参数传递的串行执行
  • parallel(tasks, [callback]) 并行执行
  • whilst(test, fn, callback) Repeatedly call fn, while test returns true. Calls callback when stopped, or an error occurs.

Deferred / Promise

  1. // 经典的针对 Promise 用途的考察
  2. doSomething().then(function () {
  3. return doSomethingElse();
  4. });
  5. doSomethin().then(function () {
  6. doSomethingElse();
  7. });
  8. doSomething().then(doSomethingElse());
  9. doSomething().then(doSomethingElse);
  • 串行式异步处理:
    • 避免:Callbacks-Hell 与 意大利面条式的代码
  1. // 比较合理的串行 Promise 方法
  2. remotedb.allDocs(...).then(functioin (resultofAllDocs) {
  3. return localdb.put(...);
  4. }).then(function (resultOfPut) {
  5. return localdb.get(...);
  6. }).then(function (resultOfGet) {
  7. return localdb.put(...);
  8. }).catch(function (err) {
  9. console.log(err);
  10. });
  • 并行式异步处理:Promise.all()Deferred.when([...])
  • 摒弃 Deferred 的思想
    • 使用 Promise 包装
  1. new Promise(function (resolve, reject) {
  2. fs.readFile('myfile.txt', function (err, file) {
  3. if (err) {
  4. return reject(err);
  5. }
  6. resolve(file);
  7. });
  8. }).then(...)
  • catch() 不可忽略
    • .catch is specified for built-in Javascript promises and is “sugar” for .then(null, function(){}).
  • 显示调用 return 方便理解代码
  • 同步和异步的模型
    • 同步解析:Promise.resolve(someSynchronousValue).then(...);
  1. getUserByName('nolan').then(function (user) {
  2. if (user.isLoggedOut()) {
  3. throw new Error('user logged out!'); // throwing a synchronous error!
  4. }
  5. if (inMemoryCache[user.id]) {
  6. return inMemoryCache[user.id]; // returning a synchronous value!
  7. }
  8. return getUserAccountById(user.id); // returning a promise!
  9. }).then(function (userAccount) {
  10. // I got a user account!
  11. }).catch(function (err) {
  12. // Boo, I got an error!
  13. });
  • 杜绝 Promise 穿透
  1. // Callback Hell
  2. getArticleList(function(articles){
  3. getArticle(articles[0].id, function(article){
  4. getAuthor(article.authorId, function(author){
  5. alert(author.email);
  6. })
  7. })
  8. })
  9. function getAuthor(id, callback){
  10. $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
  11. author: id
  12. }).done(function(result){
  13. callback(result);
  14. })
  15. }
  16. function getArticle(id, callback){
  17. $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
  18. id: id
  19. }).done(function(result){
  20. callback(result);
  21. })
  22. }
  23. function getArticleList(callback){
  24. $.ajax(
  25. "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
  26. .done(function(result){
  27. callback(result);
  28. });
  29. }
  30. // Promise
  31. getArticleList()
  32. .then(articles => getArticle(articles[0].id))
  33. .then(article => getAuthor(article.authorId))
  34. .then(author => console.log(author.email));
  35. function getAuthor(id){
  36. return new Promise(function(resolve, reject){
  37. $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
  38. author: id
  39. }).done(function(result){
  40. resolve(result);
  41. })
  42. });
  43. }
  44. function getArticle(id){
  45. return new Promise(function(resolve, reject){
  46. $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
  47. id: id
  48. }).done(function(result){
  49. resolve(result);
  50. })
  51. });
  52. }
  53. function getArticleList(){
  54. return new Promise(function(resolve, reject){
  55. $.ajax(
  56. "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
  57. .done(function(result){
  58. resolve(result);
  59. });
  60. });
  61. }
  62. // Generator
  63. function* run(){
  64. var articles = yield getArticleList();
  65. var article = yield getArticle(articles[0].id);
  66. var author = yield getAuthor(article.authorId);
  67. alert(author.email);
  68. }
  69. function runGenerator(){
  70. var gen = run();
  71. function go(result){
  72. if(result.done) return;
  73. result.value.then(function(r){
  74. go(gen.next(r));
  75. });
  76. }
  77. go(gen.next());
  78. }
  79. runGenerator();
  80. // Async
  81. async function run() {
  82. var articles = await getArticleList();
  83. var article = await getArticle(articles[0].id);
  84. var author = await getAuthor(article.authorId);
  85. alert(author.email);
  86. }

Async / Await

More Elegant Usage of Async, it is much better than just promise.

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

Processing a Mongo DB Cursor

const mongodb = require('mongodb');

test();

async function test() {
  const db = await mongodb.MongoClient.connect('mongodb://localhost:27017/test');

  await db.collection('Movies').drop();
  await db.collection('Movies').insertMany([
    { name: 'Enter the Dragon' },
    { name: 'Ip Man' },
    { name: 'Kickboxer' }
  ]);

  // Don't `await`, instead get a cursor
  const cursor = db.collection('Movies').find();
  // Use `next()` and `await` to exhaust the cursor
  for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
    console.log(doc.name);
  }
}

Process Multitasks

const bcrypt = require('bcrypt');

const NUM_SALT_ROUNDS = 8;

test();

async function test() {
  const pws = ['password', 'password1', 'passw0rd'];

  // `promises` is an array of promises, because `bcrypt.hash()` returns a
  // promise if no callback is supplied.
  const promises = pws.map(pw => bcrypt.hash(pw, NUM_SALT_ROUNDS));

  /**
   * Prints hashed passwords, for example:
   * [ '$2a$08$nUmCaLsQ9rUaGHIiQgFpAOkE2QPrn1Pyx02s4s8HC2zlh7E.o9wxC',
   *   '$2a$08$wdktZmCtsGrorU1mFWvJIOx3A0fbT7yJktRsRfNXa9HLGHOZ8GRjS',
   *   '$2a$08$VCdMy8NSwC8r9ip8eKI1QuBd9wSxPnZoZBw8b1QskK77tL2gxrUk.' ]
   */
  console.log(await Promise.all(promises));
}

Ref