资讯专栏INFORMATION COLUMN

翻译:Taming the asynchronous beast with ES7

Eastboat / 426人阅读

摘要:让我们使用它从数组中返回一个值数组在中,我们可以这样做,这是一种更简单的方法最重要的部分是创建数组,该数组立即调用所有的我们在主函数中等待这些。所以在我们真正等待完成之前,主函数就退出了。

原文:https://pouchdb.com/2015/03/0...

PouchDB最棘手的方面之一是它的API是异步的。在Stack Overflow、Github和IRC上,我看到了不少困惑的问题,而且这些问题通常是由对callbacks和promises的误解造成的。

我们真的无能为力。PouchDB是对IndexedDB, WebSQL, LevelDB (in Node), and CouchDB (via Ajax)的抽象。所有这些API都是异步的;因此PouchDB必须是异步的。

然而,当我想到优雅的数据库API时,我仍然对LocalStorage的简单性感到震惊:
    if (!localStorage.foo) {
      localStorage.foo = "bar";
    };
    console.log(localStorage.foo);
要使用LocalStorage,您只需将它当作一个神奇的javascript对象来保存数据。它使用的同步工具集与使用JavaScript本身时已经习惯的工具集相同。

对于LocalStorage的所有错误(https://www.html5rocks.com/en/tutorials/offline/quota-research/),这个API的人机工程学在很大程度上解释了它的持续流行。人们一直在使用LocalStorage,因为它很简单,而且工作正常。
Promises aren"t a panacea
对于PouchDB,我们可以尝试通过promises来减轻异步API的复杂性,这当然有助于我们摆脱pyramid of doom。

然而,promisey代码仍然很难阅读,因为promisey基本上是语言原语(如try、catch和return)的bolt-on替换:
    var db = new PouchDB("mydb");
    db.post({}).then(function (result) { // post a new doc
      return db.get(result.id);          // fetch the doc
    }).then(function (doc) {
      console.log(doc);                  // log the doc
    }).catch(function (err) {
      console.log(err);                  // log any errors
    });
作为JavaScript开发人员,我们现在有两个并行系统——sync and async——我们必须直截了当地记住这两个系统。当我们的控制流变得更复杂时,情况会变得更糟,我们需要使用promise.all()和promise.resolve()等API。或者我们只是选择了众多帮助程序库中的一个,并祈祷我们能够理解文档。

直到最近,这是我们所能期望的最好的。但所有这些都随ES7而改变。
Enter ES7
如果我告诉你,有了ES7,你可以把上面的代码改写成这样:
let db = new PouchDB("mydb");
try {
  let result = await db.post({});
  let doc = await db.get(result.id);
  console.log(doc);
} catch (err) {
  console.log(err);
}

如果我告诉你,多亏了Babel.js和Regenerator这样的工具,你现在可以将其发展到ES5并在浏览器中运行它了?
女士们先生们,请大家鼓掌,直到博文结束。
首先,让我们看看ES7是如何完成这一惊人的壮举的。
Async functions
ES7为我们提供了一种新的函数,async函数。在async函数内部,我们有一个新的关键字wait,用于“wait for”一个promise:
    async function myFunction() {
      let result = await somethingThatReturnsAPromise();
      console.log(result); // cool, we have a result
    }
如果promise resolves,我们可以在下一行立即与之交互。如果它拒绝了,那么就会抛出一个错误。所以,try/catch实际上再次有效!
    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) { 
        console.log(err); // oh noes, we got an error
      }
    }
这允许我们编写表面上看起来是同步的,但实际上是异步的代码。API返回一个promise 而不是阻塞事件循环这一事实只是一个实现细节。
还记得你什么时候可以只使用"return"和"try/catch"吗?

最好的一点是,我们今天可以把它和任何一个可以返回promises 的库一起使用。PouchDB就是这样一个库,所以让我们用它来测试我们的理论。
Managing errors and return values
首先,考虑一下pouchdb中的一个常见习惯用法:如果文档存在,我们希望按_id获取一个文档,如果不存在,则返回一个新文档。
有了promises,你就必须写下这样的东西:
    db.get("docid").catch(function (err) {
      if (err.name === "not_found") {
        return {}; // new doc
      }
      throw err; // some error other than 404
    }).then(function (doc) {
      console.log(doc);
    })
对于异步函数,这将变成:
    let doc;
    try {
      doc = await db.get("docid");
    } catch (err) {
      if (err.name === "not_found") {
        doc = {};
      } else {
        throw err; // some error other than 404
      }
    }
    console.log(doc);

    可读性更高!如果db.get()直接返回一个文档而不是一个promise,那么这几乎是我们编写的代码。唯一的区别是,当我们调用任何promise-returning函数时,必须添加wait关键字。
Potential gotchas
我在玩这个的时候遇到了一些微妙的问题,所以很高兴能意识到它们。
首先,当您等待某件事情时,您需要在一个async函数中。因此,如果您的代码严重依赖PouchDB,您可能会发现您编写了许多async函数,但很少有常规函数。
另一个更阴险的问题是,您必须小心地将代码包装在try/catch中,否则promise可能会被拒绝,在这种情况下,错误会被默默地吞没。(!)
我的建议是确保您的async函数完全被try/catch包围,至少在顶层:
async function createNewDoc() {
  let response = await db.post({}); // post a new doc
  return await db.get(response.id); // find by id
}

async function printDoc() {
  try {
    let doc = await createNewDoc();
    console.log(doc);
  } catch (err) {
    console.log(err);
  }
}
Loops
当涉及到迭代时,Async 函数会变得非常令人印象深刻。例如,假设我们希望将一些文档按顺序插入到数据库中。也就是说,我们希望这些promises一个接一个地执行,而不是同时执行。
使用标准的ES6承诺,我们必须滚动自己的promise链:
    var promise = Promise.resolve();
    var docs = [{}, {}, {}];
    
    docs.forEach(function (doc) {
      promise = promise.then(function () {
        return db.post(doc);
      });
    });
    
    promise.then(function () {
      // now all our docs have been saved
    });
这是可行的,但确实很难看。这也很容易出错,因为如果您不小心做了:
    docs.forEach(function (doc) {
      promise = promise.then(db.post(doc));
    });

然后promises实际上会同时执行,这可能会导致意想不到的结果。
但是,使用ES7,我们可以使用常规for循环:
    let docs = [{}, {}, {}];
    
    for (let i = 0; i < docs.length; i++) {
      let doc = docs[i];
      await db.post(doc);
    }
这个(非常简洁的)代码与promise链的作用是一样的!我们可以通过以下方式使其更短:
    let docs = [{}, {}, {}];
    
    for (let doc of docs) {
      await db.post(doc);
    }
注意,这里不能使用foreach()循环,如果你天真地写:
    let docs = [{}, {}, {}];
    
    // WARNING: this won"t work
    docs.forEach(function (doc) {
      await db.post(doc);
    });
然后Babel.js将失败,并出现一些不透明的错误:
    Error : /../script.js: Unexpected token (38:23)
    > 38 |     await db.post(doc);
 |           ^

这是因为在正常函数中不能使用wait。您必须使用async函数。
但是,如果您尝试使用async函数,那么您将得到一个更微妙的错误:

    let docs = [{}, {}, {}];
    
    // WARNING: this won"t work
    docs.forEach(async function (doc, i) {
      await db.post(doc);
      console.log(i);
    });
    console.log("main loop done");
这将编译,但问题是这将打印出来:
    main loop done
    0
    1
    2

发生的是,主函数提前退出,因为await实际上在子函数中。此外,这将同时执行每一个promise,这不是我们的预期。

教训是:在async函数中有任何函数时要小心。wait只会暂停它的父函数,所以检查它是否在做你认为它在做的事情。
Concurrent loops
但是,如果我们确实希望同时执行多个promises,那么使用ES7很容易实现这一点。
回想一下,有了ES6 promises,我们就有了promise.all()。让我们使用它从promises数组中返回一个值数组:        
    var docs = [{}, {}, {}];
    
    return Promise.all(docs.map(function (doc) {
      return db.post(doc);
    })).then(function (results) {
      console.log(results);
    });
在ES7中,我们可以这样做,这是一种更简单的方法:
    let docs = [{}, {}, {}];
    let promises = docs.map((doc) => db.post(doc));
    
    let results = [];
    for (let promise of promises) {
      results.push(await promise);
    }
    console.log(results);

最重要的部分是1)创建promises数组,该数组立即调用所有的promises;2)我们在主函数中等待这些promises。如果我们尝试使用Array.prototype.map,那么它将无法工作:
    let docs = [{}, {}, {}];
    let promises = docs.map((doc) => db.post(doc));
    
    // WARNING: this doesn"t work
    let results = promises.map(async function(promise) {
      return await promise;
    });
    
    // This will just be a list of promises :(
    console.log(results);

不起作用的原因是我们在等待子函数的内部,而不是主函数。所以在我们真正等待完成之前,主函数就退出了。
如果您不介意使用promise.all,也可以使用它来整理代码:
    
    let docs = [{}, {}, {}];
    let promises = docs.map((doc) => db.post(doc));
    
    let results = await Promise.all(promises);
    console.log(results);

如果我们使用数组压缩,这看起来可能会更好。然而,规范还不是最终的,所以目前Regenerator不支持它。
Caveats
ES7仍然非常前沿。Node.js 或 io.js都不支持Async函数,您必须设置一些实验标志,甚至让babel考虑它。正式来说,async/await规范(https://github.com/tc39/ecmascript-asyncawait#status-of-this-proposal)仍处于“建议”阶段。

另外,为了在ES5浏览器中工作,您还需要在您的开发代码中包含Regenerator运行时和ES6 shims。对我来说,这加起来大约60kb,缩小和gzip。对于许多开发人员来说,这实在是太多了。

然而,所有这些新工具都非常有趣,它们描绘了异步库在阳光明媚的ES7未来的美好图景。


所以,如果你想自己玩,我已经建立了一个小的演示库(https://github.com/nolanlawson/async-functions-in-pouchdb)。要开始,只需检查代码,运行npm安装和npm运行build,就可以了。关于ES7的更多信息,请看JafarHusain的演讲。
Conclusion
异步函数是ES7中的一个新概念。它们将我们丢失的returns和try/catches返回给我们,并奖励我们已经从使用新的IDIOM编写同步代码中获得的知识,这些IDIOM看起来很像旧的IDIOM,但性能更高。

最重要的是,async函数使得像PouchDB这样的API更容易使用。因此,希望这将减少用户错误和混淆,以及更优雅和可读的代码。

谁知道呢,也许人们最终会放弃LocalStorage,选择更现代的客户端数据库。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105372.html

相关文章

  • JavaScript异步编程解决方案笔记

    摘要:异步编程解决方案笔记最近读了朴灵老师的深入浅出中异步编程一章,并参考了一些有趣的文章。另外回调函数中的也失去了意义,这会使我们的程序必须依赖于副作用。 JavaScript 异步编程解决方案笔记 最近读了朴灵老师的《深入浅出NodeJS》中《异步编程》一章,并参考了一些有趣的文章。在此做个笔记,记录并巩固学到的知识。 JavaScript异步编程的两个核心难点 异步I/O、事件驱动使得...

    dmlllll 评论0 收藏0
  • Javascript的模块管理 CMD AMD ES7

    摘要:一一开始是垃圾,但随着时代的发展业务的进步,变得越来越重要,但涉及之初就是用来打杂的,有缺陷如下简单翻译下没有模块系统没有标准库没有文件没有系统没有标准接口,用来做服务器或者数据库没有依赖包管理系统。 一 Commonjs 一开始js是垃圾,但随着时代的发展、业务的进步,js变得越来越重要,但js涉及之初就是用来打杂的,有缺陷如下: JavaScript has no module ...

    darryrzhong 评论0 收藏0
  • 通过ES6 Generator函数实现异步流程

    摘要:换句话说,我们很好的对代码的功能关注点进行了分离通过将使用消费值得地方函数中的逻辑和通过异步流程来获取值迭代器的方法进行了有效的分离。但是现在我们通过来管理代码的异步流程部分,我们解决了回调函数所带来的反转控制等问题。 本文翻译自 Going Async With ES6 Generators 由于个人能力知识有限,翻译过程中难免有纰漏和错误,还望指正Issue ES6 Gener...

    刘厚水 评论0 收藏0
  • ES6中的异步编程:Generators函数+Promise:最强大的异步处理方式

    摘要:更好的异步编程上面的方法可以适用于那些比较简单的异步工作流程。小结的组合目前是最强大,也是最优雅的异步流程管理编程方式。 访问原文地址 generators主要作用就是提供了一种,单线程的,很像同步方法的编程风格,方便你把异步实现的那些细节藏在别处。这让我们可以用一种很自然的方式书写我们代码中的流程和状态逻辑,不再需要去遵循那些奇怪的异步编程风格。 换句话说,通过将我们generato...

    Taonce 评论0 收藏0
  • 2017-07-10 前端日报

    摘要:前端日报精选入门指南入口,输出,加载器和插件中数据类型转换让我印象深刻的面试题大话大前端时代一与的组件化庖丁解牛一发布中文第期手把手教你用管理状态上个快速编程技巧众成翻译中执行顺序组件解耦之道众成翻译组件模型启示录有个梨作 2017-07-10 前端日报 精选 Webpack入门指南: 入口,输出,加载器和插件JavaScript中数据类型转换让我印象深刻的javascript面试题大...

    Heier 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<