JavaScript 开发者们,现在是时候承认一个事实了:我们在 promises 的使用上还存在问题。但并不是 promises 本身有问题,被 A+标准 定义的 promises 是极好的。
在过去一年的课程中揭示给我的一个比较大的问题是,正如我所看到的,很多程序员在使用 PouchDB API 以及与其他重 promise 的 API 的过程中存在的一个问题是:
我们一部分人在使用 promises 的过程中并没有真正的理解 promises。
如果你觉得这不可思议,那么考虑下我最近在Twitter上的写的一个比较难的题目:
问题:下面 4 个 promises 有什么区别呢?
1 2 3 4 5 6 7 8 9 10 11 |
doSomething().then(function () { return doSomethingElse(); }); doSomething().then(function () { doSomethingElse(); }); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse); |
如果你知道答案,那么恭喜你:你是一个 promises 武士。我觉得你可以不用再继续读这篇博客文章。
对其他的 99.99% 的人,你们都在很好的公司上班。但是在回复我推特的人中没有人能解决这个问题,而且我对3楼的回答感到特别惊讶。是的,尽管我写了测试案例!
答案在这篇文章的末尾,但是首先,我想在第一时间搞清楚为什么promises是如此的棘手,以及为什么我们这么多人,不管是新手还是像专家的人,都会被它们搞晕。我也将提供我认为的我对其独特的洞察力,用一个独特的技巧,使得对promises的理解成为有把握的事情。是的,在这之后我认为它们真的不是那么难。
但是在开始之前,让我们挑战一下对于promises常见的一些假设。
为什么要用promises?
如果你读过关于promises的一些文章,你经常会发现对《世界末日的金字塔》这篇文章的引用,会有一些可怕的逐渐延伸到屏幕右侧的回调代码。
promises确实解决了这个问题,但它并不只是关乎于缩进。正如在优秀的演讲《回调地狱的救赎》中所解释的那样,回调真正的问题在于它们剥夺了我们对一些像return和throw的关键词的使用能力。相反,我们的程序的整个流程都会基于一些副作用。一个函数单纯的调用另一个函数。
事实上,回调做了很多更加险恶的事情:它们剥夺了我们的堆栈,这些是我们在编程语言中经常要考虑的。没有堆栈来书写代码在某种程度上就好比驾车没有刹车踏板:你不会知道你是多么需要它,直到你到达了却发现它并不在这。
promises的全部意义在于它给回了在函数式语言里面我们遇到异步时所丢失的return,throw和堆栈。为了更好的从中获益你必须知道如何正确的使用promises。
新手常犯的错误
一些人尝试解释promises是卡通,或者是一种名词导向的方式:“你可以传递的东西就是代表着异步值”。
我并没有发现这些这些解释多么有帮助。对于我来说,promises就是关乎于代码结构和流程。因此我认为,过一下一些常见的错误以及展示出如何修复它们是更好的方式。我把这称为“新手常犯的错误”的意义就在于,“你现在是个新手,初出茅庐,但你很快会成为专业人士”。
说一点题外话:“promises”对于不同的人有不同的理解,但是这篇文章的目的在于,我只是谈论官方标准,正如在现代浏览器中所暴露的window.Promise API。尽管不是所有的浏览器都支持window.Promise,对于一个很好的补充,可以查看名为Lie的项目,它是一个实现promises的遵循规范的最小集。
新手常见错误#1:世界末日的promise金字塔
看下开发人员怎么使用拥有大量基于promise的API的PouchDB,我发现了很多不好的promise模式。最常见的不好实践是这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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的,而且是的,那有点像使用一个很有威力的磨砂机来打磨你的指甲,但是你是可以做到的。
如果你认为这一类型的错误会仅限于绝对的新手,你会惊讶的发现上面的代码就来自于黑莓开发者的官方博客。老的回调习惯很难改(致开发者:很抱歉拿你来举例,但是你的代码很有教育意义)。
一个更好的方式是:
1 2 3 4 5 6 7 8 9 |
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); }); |
这被称为组成式promises(composing promises),它是有超能力的promises之一。每一个函数都在前面的promise被resolve之后被调用,而且将前面的promise的输出作为参数被调用。稍后将详细介绍。
新手常见错误#2:尼玛,我该怎么对Promises调用forEach()呢?
这是大多数人开始理解Promises要突破的地方。尽管他们能熟悉forEach()循环(或者for循环,或者while循环),他们并不知道如何对Promises使用这些循环。此时,他们写的代码会像是这样:
1 2 3 4 5 6 7 8 |
// 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 JavaScript 开发者们,现在是时候承认一个事实了:我们在 promises 的使用上还存在问题。但并不是 promises 本身有问题,被 A+标准 定义的 promises 是极好的。 在过去一年的课程中揭示给我的一个比较大的问题是,正如我所看到的,很多程序员在使用 PouchDB API 以及与其他重 promise 的 API 的过程中存在的一个问题是: 我们一部分人在使用 promises 的过程中并没有真正的理解 promises。如果你觉得这不可思议,那么考虑下我最近在Twitter上的写的一个比较难的题目: 问题:下面 4 个 promises 有什么区别呢?
如果你知道答案,那么恭喜你:你是一个 promises 武士。我觉得你可以不用再继续读这篇博客文章。 对其他的 99.99% 的人,你们都在很好的公司上班。但是在回复我推特的人中没有人能解决这个问题,而且我对3楼的回答感到特别惊讶。是的,尽管我写了测试案例! 答案在这篇文章的末尾,但是首先,我想在第一时间搞清楚为什么promises是如此的棘手,以及为什么我们这么多人,不管是新手还是像专家的人,都会被它们搞晕。我也将提供我认为的我对其独特的洞察力,用一个独特的技巧,使得对promises的理解成为有把握的事情。是的,在这之后我认为它们真的不是那么难。 但是在开始之前,让我们挑战一下对于promises常见的一些假设。 为什么要用promises?如果你读过关于promises的一些文章,你经常会发现对《世界末日的金字塔》这篇文章的引用,会有一些可怕的逐渐延伸到屏幕右侧的回调代码。 promises确实解决了这个问题,但它并不只是关乎于缩进。正如在优秀的演讲《回调地狱的救赎》中所解释的那样,回调真正的问题在于它们剥夺了我们对一些像return和throw的关键词的使用能力。相反,我们的程序的整个流程都会基于一些副作用。一个函数单纯的调用另一个函数。 事实上,回调做了很多更加险恶的事情:它们剥夺了我们的堆栈,这些是我们在编程语言中经常要考虑的。没有堆栈来书写代码在某种程度上就好比驾车没有刹车踏板:你不会知道你是多么需要它,直到你到达了却发现它并不在这。 promises的全部意义在于它给回了在函数式语言里面我们遇到异步时所丢失的return,throw和堆栈。为了更好的从中获益你必须知道如何正确的使用promises。 新手常犯的错误一些人尝试解释promises是卡通,或者是一种名词导向的方式:“你可以传递的东西就是代表着异步值”。 我并没有发现这些这些解释多么有帮助。对于我来说,promises就是关乎于代码结构和流程。因此我认为,过一下一些常见的错误以及展示出如何修复它们是更好的方式。我把这称为“新手常犯的错误”的意义就在于,“你现在是个新手,初出茅庐,但你很快会成为专业人士”。 说一点题外话:“promises”对于不同的人有不同的理解,但是这篇文章的目的在于,我只是谈论官方标准,正如在现代浏览器中所暴露的window.Promise API。尽管不是所有的浏览器都支持window.Promise,对于一个很好的补充,可以查看名为Lie的项目,它是一个实现promises的遵循规范的最小集。 新手常见错误#1:世界末日的promise金字塔看下开发人员怎么使用拥有大量基于promise的API的PouchDB,我发现了很多不好的promise模式。最常见的不好实践是这个:
是的,事实证明你是能使用像回调的promises的,而且是的,那有点像使用一个很有威力的磨砂机来打磨你的指甲,但是你是可以做到的。 如果你认为这一类型的错误会仅限于绝对的新手,你会惊讶的发现上面的代码就来自于黑莓开发者的官方博客。老的回调习惯很难改(致开发者:很抱歉拿你来举例,但是你的代码很有教育意义)。 一个更好的方式是:
这被称为组成式promises(composing promises),它是有超能力的promises之一。每一个函数都在前面的promise被resolve之后被调用,而且将前面的promise的输出作为参数被调用。稍后将详细介绍。 新手常见错误#2:尼玛,我该怎么对Promises调用forEach()呢?这是大多数人开始理解Promises要突破的地方。尽管他们能熟悉forEach()循环(或者for循环,或者while循环),他们并不知道如何对Promises使用这些循环。此时,他们写的代码会像是这样:
|