有关Promises异步问题详解

迄今为止,可能每个JavaScript开发者和他们的祖母都听说过Promises。如果你没有,那么你即将会。promises的概念是由CommonJS小组的成员在 Promises/A规范 中提出来的。Promises被逐渐用作一种管理异步操作回调的方法,但出于它们的设计,它们远比那个有用得多。事实上,由于它们的多种用法,有无数人告诉我——在我写过一些关于promises的东西后——我“遗漏了promises的重点”。那么什么是promises的重点呢?

一点关于Promises的东西

在我开始promise的“重点”之前,我想我应该给你一点它们如何工作的内貌。一个promise是一个对象——根据Promise/A规范——只需要一个方法:then。then方法带有三个参数:一个成功回调,一个失败回调,和一个前进回调(规范没有要求包括前进回调的实现,但是很多都实现了)。一个全新的promise对象从每个then的调用中返回。
一个promise可以是三种状态之一:未完成的,完成的,或者失败的。promise以未完成的状态开始,如果成功它将会是完成态,如果失败将会是失败态。当一个promise移动到完成态,所有注册到它的成功回调将被调用,而且会将成功的结果值传给它。另外,任何注册到promise的成功回调,将会在它已经完成以后立即被调用。

同样的事情发生在promise移动到失败态的时候,除了它调用的是失败回调而不是成功回调。对包含前进特性的实现来说,promise在它离开未完成状态以前的任何时刻,都可以更新它的progress。当progress被更新,所有的前进回调(progress callbacks)会被传递以progress的值,并被立即调用。前进回调被以不同于成功和失败回调的方式处理;如果你在一个progress更新已经发生以后注册了一个前进回调,新的前进回调只会在它被注册以后被已更新的progress调用。
我们不会进一步深入promise状态是如何管理的,因为那不在规范之内,而且每个实现都有差别。在后面的例子中,你将会看到它是如何完成的,但目前这就是所有你需要知道的。

处理回调

像前面提到的为异步操作处理回调,是promises的最基本和最普通的用途,让我们将一个标准的回调与一个采用了promise的回调比较一下。

// Normal callback usage
asyncOperation(function() {
  // Here's your callback
});
// Now `asyncOperation` returns a promise
asyncOperation().then(function(){
  // Here's your callback
});

我很怀疑只是看到这个例子的话是否有人会真的关心去使用promises。看起来没有什么好处,除了“then”使得在异步操作完成之后的回调函数被调用这件事看起来更加明显。但是即使是这个好处,我们现在有了更多的代码(抽象应该使我们的代码更短,不是吗?)而且promise比标准回调稍微性能差一点。

但是,不要让这阻碍到你。如果这就是promise可以做的最好的事,这篇文章就不会存在了

厄运的金字塔

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(function(data){
  // Do some processing with `data`
  anotherAsync(function(data2){
    // Some more processing with `data2`
    yetAnotherAsync(function(){
      // Yay we're finished!
    });
  });
});
// Let's look at using promises
asyncOperation()
.then(function(data){
  // Do some processing with `data`
  return anotherAsync();
})
.then(function(data2){
  // Some more processing with `data2`
  return yetAnotherAsync();
})
.then(function(){
  // Yay we're finished!
});

正如你所见,promises的使用使得事情变扁平而且更可读了。这能起作用是因为——像早先提到的——then返回了一个promise,所以你可以将then的调用不停的串连起来。由then返回的promise装载了由调用返回的值。如果调用返回了一个promise(像这个例子中的情形一样),then返回的 promise装载了与你的回调返回的promise所装载的相同值。这内容很多,因此我将帮助你一步一步的理解它

异步操作返回一个promise对象。因此我们在那个promise对象中调用then,并且传给它一个回调函数;then也会返回一个promise。当异步操作结束,它将给promise装上数据。然后(第一次)回调被调用,数据被作为参数传递进去。如果回调不含有返回值,then返回的promise将会立即不带值组装。如果回调返回的不是一个promise,那么then返回的 promise将会立即装载那个数值。如果回调返回一个promise(像例子中的),那么then返回的 promise将等待直到我们回调返回的promise被完全装载。一旦我们回调的 promise被装载,它装载的值(本例中就是data2)将会被提交给then的promise。然后then中的promise装载了data2。等等。听起来有点复杂,但事实上很简单,如果我说的你不能理解,我非常抱歉。我猜我可能不是谈论它的最佳人选。

用命名的回调替代

但显然 promises 不是使这个结构扁平化的唯一方法。在写了一篇提到promises解决了厄运的金字塔问题的帖子之后,有个人对该帖评论说……

我想promises有时是有用的,但是“嵌套”的回调的问题(圣诞树综合症)可以仅用一个命名的函数作为一个参数替代匿名函数的方法平常的处理:

asyncCall( param1, param2, HandlerCallback );
function HandlerCallback(err, res){
// do stuff
}

它的例子只是给出了一层深的例子,但它仍是正确的。我们来扩展我前面的例子,使这个看起来容易些。

命名回调

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(handler1);
function handler1(data) {
  // Do some processing with `data`
  anotherAsync(handler2);
}
function handler2(data2) {
  // Some more processing with `data2`
  yetAnotherAsync(handler3);
}
function handler3() {
  // Yay we're finished!
}

看看上面的代码!他们绝对是对的!它就是一个扁平的结构,但是这里有个问题同样也存在于 我以前从来没有注意过的老的回调例子中:依赖性和复用性。依赖性和复用性是相互关联的可逆类型。一样东西依赖的越少,那么它的复用性就越大。在以上的例子中,handler1依赖handler2,handler2依赖handler3.这就意味着handler1无论出于任何目的都不可在被用除非handler2也呈现出来。假如你不打算重用他们,那么给你的函数命名又有什么意义呢?

最糟糕的的是handler1都不关心在handler2里面发生了什么事情。它压根就不需要handler2除了和它异步工作。因此,让我们消除这些依赖性然后通过用promise使函数更具复用性。

链式回调

asyncOperation().then(handler1).then(handler2).then(handler3);
function handler1(data) {
  // Do some processing with `data`
  return anotherAsync();
}
function handler2(data2) {
  // Some more processing with `data2`
  return yetAnotherAsync();
}
function handler3() {
  // Yay we're finished!
}

这样看起来是不是好多了?假如另外的函数存在的话,现在handler1和handler2都互不相关了。想看看他们是否真的很棒呢?现在handler1可以被用在不需要handler2的情况下了。相反,handler1被操作以后,我们将可以用另一个handler。

复用函数

asyncOperation().then(handler1).then(anotherHandler);
function handler1(data) {
  // Do some processing with `data`
  return anotherAsync();
}
function anotherHandler(data2) {
  // Do some really awesome stuff that you've never seen before. It'll impress you
}

现在handler1已经从handler2脱离而且可以被用在了更多的情形中,特别是那些由handler2提供的功能而我们又不想用的。这就是复用性!评论家解决代码易读性的唯一方法就是通过消除缩进。我们不想消除缩进仅仅是为了缩进。多层次的缩进仅仅是某些事情错误的标志,问题不一定在它本身。他就像是由脱水引起的头痛。真正的问题是脱水,不是头痛。解决的方法是获得水合物,而不是用一些止痛药。

并行异步操作

在前面我提到的文章里,我将promises与events在处理异步操作方面做了比较。遗憾的是,按照那些曾提到过的人在评论里给的说法,我比较的不是很成功。我描述出了promises的力量,接着转到events来描述它们的力量,就像在我的特别项目里用到的那样。没有比较和对比。一位评论者写道(修改了一点语法错误):

我想用帖子中的例子是一个坏的对照。有篇论文证明了promises的值将会怎样,如果按下虚构的“启动服务器按钮”,将不仅仅是启动一个web服务器,还有一个数据库服务器,当它们都在运行的时候只是更新了UI。

使用promise的.when方法将会使这种“多个异步操作”例子变得普通,然而响应多个异步事件需要一个并不普通的代码量。
他完全正确。事实上我没有比较那两种情况。那篇文章的要点实际在于说明promises不是异步操作的唯一机制,而且在一些情况下,它们也不一定是最好的。在这个评论者指出的情况下,promises当然是最佳的解决办法。我们来看看他说的是什么

jQuery 具有 一个名为when的方法 ,可以带上任意数量的promise参数,并返回一个单一的promise。如果任何一个promise传入失败,when返回的promise也会失败。如果所有的promises被装载,那么每个值都将会按照promises被定义的顺序传递给附加的回调。

以并行的方式执行无数的异步操作非常有用,然后只要在它们之中的每一个结束之后继续执行回调。我们看一个简单的例子。

jQuery.when

// Each of these async functions return a promise
var promise1 = asyncOperation1();
var promise2 = asyncOperation2();
var promise3 = asyncOperation3();
// The $ refers to jQuery
$.when(promise1, promise2, promise3).then(
  function(value1, value2, value3){
    // Do something with each of the returned values
  }
);

人们经常说这是 promises 带来的最好的东西之一,也是 promises 的一部分重要的意义所在。我也认为这是个简化了大量操作的好特性,但是这种 when 方法的机制 根本就没有在任何 Promises 规范中提到,所以我不认为它是 Promises意义所在。有一个规范提到了 when 方法,但是和上面的完全不同。就我所知,jQuery 是唯一的实现了这种 when 方法的库。其他的 promises 库,例如  Q, Dojo, 和  when 依照  Promises/B spec 实现了 when 方法, 但是并没有实现注释者提及的 when 方法。但是,Q 库有一个   all方法,when.js 也有一个  parallel方法,与上面的 jQuery.when 方法作用一样,只是它们接受一个数组类型的参数,而不是任意数量的参数。

值的表示

Promise是处理以下场景的更好的方法:

"我想在这个数据库中找一个用户,但find方法是异步的。"

因此,这里我们有了一个不能立刻返回值的find方法。但最终它确实"返回"了一个数值(通过一个回调的方式),而你希望以某种方式处理那个数值。现在,通过使用一个回调,你能定义一个继续部分,或者说“一些将在以后时间里处理那个数值的代码”

Promise改变了那种“嘿,这里是一些你会发现你用来处理返回数值的代码”。它们是一些允许"find"方法说“嘿,我将忙着找你要找的信息,但与此同时你能继续等着返回结果,而且你能同时以任何你希望的方式处理它,就像实际的东西!”

Promise代表了真实的数值。那就是陷阱。它们工作在你像处理实际东西一样处理Promise的时候。Promise的JavaScript实现期待你给它传递一个回调函数,这只是一个“巧合”,它不是重要的事情。

我相信这真的就是promise的重点。为什么?读一读 Promise/A规范 的第一句“一个promise代表了一个操作的一次完成最终返回的数值。“使它有点明显了,是不是?好吧,即使那就是重点,那也不能阻止我在后面本文中呈现其他人的见解。不管怎么说,我们再多谈论这个思想一点。

结论

promise的重点是它代表一个操作返回的最终结果值,但使用它们的原因是使同步操作更好的并行。自从异步编程进入此场景,到处都是弹出的回调,以奇怪的方式遮住我们的代码。Promise是一种改变其的方法。Promise允许我们以同步的方式写代码,同时给予我们代码的异步执行。

(0)

相关推荐

  • Javascript中的异步编程规范Promises/A详细介绍

    Javascript里异步编程逐渐被大家接受,先前大家一般通过回调嵌套,setTimeout.setInterval等方式实现,代码看起来非常不直观,不看整个代码逻辑很难快速理解.Javascript里异步函数大概有I/O函数(Ajax.postMessage.img load.script load等).计时函数(setTimeout.setInterval)等. 这些我们都很熟悉,在复杂的应用中往往会嵌套多层,甚至以为某些步骤未完成而导致程序异常,最简单的例子:比如你往DOM中注入节点,你必

  • JavaScript异步回调的Promise模式封装实例

    网页的交互越来越复杂,JavaScript 的异步操作也随之越来越多.如常见的 ajax 请求,需要在请求完成时响应操作,请求通常是异步的,请求的过程中用户还能进行其他的操作,不会对页面进行阻塞,这种异步的交互效果对用户来说是挺有友好的.但是对于开发者来说,要大量处理这种操作,就很不友好了.异步请求完成的操作必须预先定义在回调函数中,等到请求完成就必须调用这个函数.这种非线性的异步编程方式会让开发者很不适应,同时也带来了诸多的不便,增加了代码的耦合度和复杂性,代码的组织上也会很不优雅,大大降低了

  • NodeJS中利用Promise来封装异步函数

    在写Node.js的过程中,连续的IO操作可能会导致"金字塔噩梦",回调函数的多重嵌套让代码变的难以维护,利用CommonJs的Promise来封装异步函数,使用统一的链式API来摆脱多重回调的噩梦. Node.js提供的非阻塞IO模型允许我们利用回调函数的方式处理IO操作,但是当需要连续的IO操作时,你的回调函数会多重嵌套,代码很不美观,而且不易维护,而且可能会有许多错误处理的重复代码,也就是所谓的"Pyramid of Doom". 复制代码 代码如下: ste

  • javascript使用Promise对象实现异步编程

    Promise对象是CommonJS工作组为异步编程提供的统一接口,是ECMAScript6中提供了对Promise的原生支持,Promise就是在未来发生的事情,使用Promise可以避免回调函数的层层嵌套,还提供了规范更加容易的对异步操作进行控制.提供了reject,resolve,then和catch等方法. 使用PROMISE Promise是ES6之后原生的对象,我们只需要实例化Promise对象就可以直接使用. 实例化Promise: var promise = new Promis

  • Javascript异步编程模型Promise模式详细介绍

    Promise 编程模式也被称为 thenable,可以理解为 延迟后执行.每个 Promise 都拥有一个叫做 then 的唯一接口,当 Promise 失败或成功时,它就会进行回调.它代表了一种可能会长时间运行而且不一定必须完成的操作结果.这种模式不会阻塞和等待长时间的操作完成,而是返回一个代表了承诺的(promised)结果的对象. 当前的许多 JavaScript 库(如 jQuery 和 Dojo.AngularJS)均添加了这种称为 Promise 的抽象.通过这些库,开发人员能够在

  • 简单实现异步编程promise模式

    异步编程 javascript异步编程, web2.0时代比较热门的编程方式,我们平时码的时候也或多或少用到,最典型的就是异步ajax,发送异步请求,绑定回调函数,请求响应之后调用指定的回调函数,没有阻塞其他代码的执行.还有像setTimeout方法同样也是异步执行回调的方法. 如果对异步编程还不太熟悉的话,直接戳 阮一峰大牛的教程 ,这篇文章介绍了四种异步编程的方式: 回调函数 事件监听 发布/订阅 promise模式 这几种方式的可维护性逐级递增,理解难度也逐级递增.这篇总结也是针对prom

  • javascript异步编程代码书写规范Promise学习笔记

    最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编程时候代码书写的方式产生的. 随着javascript的发展,异步的场景越来越多.前端有AJAX,setTimeout等,后端Node异步更多.按照传统的做法,那么就是各种回调嵌回调.代码可以把人绕晕. 这个时候,CommonJS社区提出了一个叫做Promise/A+的规范,这个规范定义了如何书写异步代

  • JavaScript异步编程Promise模式的6个特性

    在我们开始正式介绍之前,我们想看看Javascript Promise的样子: 复制代码 代码如下: var p = new Promise(function(resolve, reject) {  resolve("hello world");}); p.then(function(str) {  alert(str);}); 1. then()返回一个Forked Promise 以下两段代码有什么区别呢? 复制代码 代码如下: // Exhibit Avar p = new Pr

  • 异步JavaScript编程中的Promise使用方法

    异步? 我在很多地方都看到过异步(Asynchronous)这个词,但在我还不是很理解这个概念的时候,却发现自己常常会被当做"已经很清楚"(* ̄? ̄). 如果你也有类似的情况,没关系,搜索一下这个词,就可以得到大致的说明.在这里,我会对JavaScript的异步做一点额外解释. 看一下这段代码: var start = new Date(); setTimeout(function(){ var end = new Date(); console.log("Time elap

  • 使用Promise解决多层异步调用的简单学习心得

    前言 第一次接触到Promise这个东西,是2012年微软发布Windows8操作系统后抱着作死好奇的心态研究用html5写Metro应用的时候.当时配合html5提供的WinJS库里面的异步接口全都是Promise形式,这对那时候刚刚毕业一点javascript基础都没有的我而言简直就是天书.我当时想的是,微软又在脑洞大开的瞎捣鼓了. 结果没想到,到了2015年,Promise居然写进ES6标准里面了.而且一项调查显示,js程序员们用这玩意用的还挺high. 讽刺的是,作为早在2012年就在M

随机推荐