[翻译] We have a problem with promises

您所在的位置:网站首页 problem是什么意思翻译中文 [翻译] We have a problem with promises

[翻译] We have a problem with promises

#[翻译] We have a problem with promises| 来源: 网络整理| 查看: 265

原文地址: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html 已取得原作者同意

用Javascript的小伙伴们,是时候承认了,关于 promises 我们一直存在着问题。并非说 promises 本身有问题,Promises/A+ 是极好的。

就我过去数年观察大量 PouchDB API 以及其他 promise-heavy API 的使用者们与这些 API 的搏斗中我发现,最大的问题是:

大部分使用 promises 的小伙伴们并没有真正的理解它

如果你不认同这个观点,可以看看我最近在 twitter 上出的这道题:

Q: 下面的四种 promises 的区别是什么

doSomething().then(function () { return doSomethingElse(); }); doSomething().then(function () { doSomethingElse(); }); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse);

如果你知道正常答案,那么我要恭喜你,你是一位 promises 大拿,你完全可以不再继续阅读这篇文件。

另外 99.99% 的小伙伴们,你们才是正义。没有一个人在 twitter 上给出正确答案,甚至我自己也被 #3 的答案惊到了。恩,即使这道题是我自己出的。

正确答案在这篇文章的结尾,在此之前,我希望首先探究一下为何为何 promises 如此复杂,并且为何有这么多人,无论是新手还是专家,都被它坑到了。同时我还会给出一个我自认为非常独特的视角,可以让 promises 变的更加容易理解。同时,我非常确信在了解这些之后,promises 并不会再难以理解。

不过在开始这些之前,让我们先了解一些 promises 的基础知识。

Promises 的起源

如果你阅读了 promises 的一些相关文献,你会发现有一个词 金字塔问题 经常出现。它描述的是大量的回调函数慢慢向右侧屏幕延伸的一种状态。

Promises 的确解决了这个问题,并且不仅仅是缩进问题。就像在 Callback Hell的救赎 中描述的一样,回调函数真正的问题在于他剥夺了我们使用 return 和 throw 这些关键字的能力。相反,我们的整个代码流程都是基于副作用的: 一个函数会附带调用其他函数。

原文关于副作用的描述并不能很直观的进行理解,建议参考 WIKI 。简单来说就是一个函数除了会返回一个值之外,还会修改函数以外的状态如全局变量等等。实际上所有异步调用都可以视为带有副作用的行为。译者注。

并且实际上,回调更加恼人的是,他会将我们通常在大部分编程语言中能获得的 堆栈 破坏。编写代码时如果无法获取堆栈就像开车时没有刹车一样: 不到用的时候,你不会知道它有多重要。

Promises 给予我们的就是在我们使用异步时丢失的最重要的语言基石: return, throw 以及堆栈。但是想要 promises 能够提供这些便利给你的前提是你知道如何正确的使用它们。

新手错误

一些同学试图通过用 卡通 来描述 promises,或者试图用语言去描述它: “哦,你可以把它作为一个异步的值进行传递。”

我认为这些解释并不会有很大的帮助。对我来说,promises 完全是一种代码结构和流程。因此我认为直接展示一些常见的错误并且演示如何修复它们更能够说明问题。我说这些问题是 “新手问题” ,这意味着 “虽然你现在是一个新手,孩子,但是马上你会变成一位专家”。

小插曲: “promises” 对于不同的人有不同的理解和观点,但是在这篇文章中我特指 正式标准 ,在现代浏览器中暴露为 window.Promise。虽然并非所有浏览器都有 windows.Promise,但是可以寻找一些 pollyfill ,比如 Lie 是目前体积最小的兼容标准的库。

新手错误 #1: promise版的金字塔问题

观察大家如何使用 PouchDB 这类大型的 promise 风格的API,我发现大量错误的 promise 使用形式。最常见的错误就是下面这个:

remotedb.allDocs({ include_docs: true, attachments: true }).then(function (result) { var docs = result.rows; docs.forEach(function(element) { localdb.put(element.doc).then(function(response) { alert("Pulled doc with id " + element.doc._id + " and added to local db."); }).catch(function (err) { if (err.status == 409) { localdb.get(element.doc._id).then(function (resp) { localdb.remove(resp._id, resp._rev).then(function (resp) { // et cetera...

是的,实际上你可以像使用回调一样使用 promises,恩,就像用打磨机去削脚趾甲一样,你确实可以这么做。

并且如果你以为这样的错误只限于初学者,那么你会惊讶于我实际上是在黑莓官方开发者博客上看到上面的代码。老的回调风格的习惯难以消灭。(至开发者: 抱歉选了你的例子,但是你的例子将会有积极的教育意义)

正确的风格应该是这样:

remotedb.allDocs(...).then(function (resultOfAllDocs) { return localdb.put(...); }).then(function (resultOfPut) { return localdb.get(...); }).then(function (resultOfGet) { return localdb.put(...); }).catch(function (err) { console.log(err); });

这种写法被称为 composing promises ,是 promises 的强大能力之一。每一个函数只会在前一个 promise 被调用并且完成回调后调用,并且这个函数会被前一个 promise 的输出调用,稍后我们在这块做更多的讨论。

新手错误 #2: WTF, 用了 promises 后怎么用 forEach?

这里是大多数人对于 promises 的理解开始出现偏差。一旦当他们要使用他们熟悉的 forEach() 循环 (无论是 for 循环还是 while 循环),他们完全不知道如何将 promises 与其一起使。因此他们就会写下类似这样的代码。

// I want to remove() all docs db.allDocs({include_docs: true}).then(function (result) { result.rows.forEach(function (row) { db.remove(row.doc); }); }).then(function () { // I naively believe all docs have been removed() now! });

这份代码有什么问题?问题在于第一个函数实际上返回的是 undefined,这意味着第二个方法不会等待所有 documents 都执行 db.remove()。实际上他不会等待任何事情,并且可能会在任意数量的文档被删除后执行!

这是一个非常隐蔽的 bug,因为如果 PouchDB 删除这些文档足够快,你的 UI 界面上显示的会完成正常,你可能会完全注意不到有什么东西有错误。这个 bug 可能会在一些古怪的竞态问题或一些特定的浏览器中暴露出来,并且到时可能几乎没有可能去定位问题。

简而言之,forEach()/for/while 并非你寻找的解决方案。你需要的是 Promise.all():

db.allDocs({include_docs: true}).then(function (result) { return Promise.all(result.rows.map(function (row) { return db.remove(row.doc); })); }).then(function (arrayOfResults) { // All docs have really been removed() now! });

上面的代码是什么意思呢?大体来说,Promise.all()会以一个 promises 数组为输入,并且返回一个新的 promise。这个新的 promise 会在数组中所有的 promises 都成功返回后才返回。他是异步版的 for 循环。

并且 Promise.all() 会将执行结果组成的数组返回到下一个函数,比如当你希望从 PouchDB 中获取多个对象时,会非常有用。此外一个更加有用的特效是,一旦数组中的 promise 任意一个返回错误,Promise.all() 也会返回错误。

新手错误 #3: 忘记使用 .catch()

这是另一个常见的错误。单纯的坚信自己的 promises 会永远不出现异常,很多开发者会忘记在他们的代码中添加一个 .catch()。然而不幸的是这也意味着,任何被抛出的异常都会被吃掉,并且你无法在 console 中观察到他们。这类问题 debug 起来会非常痛苦。

类似 Bluebird 之类的 Promise 库会在这种场景抛出 UnhandledRejectionError 警示有未处理的异常,这类情况一旦发现,就会造成脚本异常,在 Node 中更会造成进程 Crash 的问题,因此正确的添加 .catch() 非常重要。 译者注

为了避免这类讨厌的场景,我习惯于像下面的代码一样使用 promise:

somePromise().then(function () { return anotherPromise(); }).then(function () { return yetAnotherPromise(); }).catch(console.log.bind(console)); //


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3