Blog

Blog

PHODAL

Javascript Promise模式——相当酷的Callback Hell终结者

遇到了一些关于Nodejs Callback的坑,于是只能去翻译一些文章来解决当前的问题。

原文: JavaScript Promises Are Cool

“And when I promise something, I never ever break that promise. Never.” ― Rapunzel

多数语言都有有趣的方案的库被称为Promises, deferreds, 又或者是future. 这些库可以帮助我们驾驭这些原生的异步成更平凡的顺序。 JavaScript的Promises可以促进关注点分离代替紧密耦合接口。

适用于Promises的情形:

  • 执行规则
  • 多个远程验证
  • 操作超时
  • 远程数据请求
  • 动画
  • 从应用程序逻辑去耦事件逻辑
  • 消除Callback的厄运三角
  • 控制并行异步操作

JavaScript Promises是会在将来返回一个值的欠条。它是具有良好定义的行为的数据对象。Promises有三种可能的状态之一:

  • Pending
  • Rejected
  • Resolved

一个rejected或resolved promise是固定的。一个Promise状态只能从pending到固定的。此后它的状态是不可改变的。一个Promise可以在他关联的动作固定之后保持相当长时间。需要时,我们可以多次提取结果。我们可以通过调用promise.then()实现这一点。仅当相关的动作执行完,该调用才会返回。同时,我们可以流畅使用链式Promise。每个链式的"Then"功能要么返回一个Promise,要么返回原始的Promise值。

Through this paradigm we can write asynchronous code more as if it were synchronous code. The power lies in composing promise tasks:

Stacked tasks: multiple thens scattered in the code, but on same promise. Parallel tasks: multiple promises return a single promise. Sequential tasks: promise … then … promise Combinations of these. Why this extra layer? Why can’t we just use raw callbacks?

通过这个范例,我们可以写出更多的异步代码,同时看上去像是同步代码。动力就在于构建Promise的任务:

  • 堆叠任务:散落在代码中的多个thens,但是是同样的Promise。
  • 并行任务:多个Promise返回一个Promise。
  • 连续的任务:Promise..然后...Promise。
  • 组合上述的任务。

为什么这些额外的层?为什么我们不能只使用原始回调?

Callbacks的问题

对于由点击触发的表单值与存储REST响应的结果之类的简单的经常性事件来说,Callbacks是一个不错的解决方案。Callbacks也诱使一个代码在一个链式具有一个REST的回调,接着又提供了一个回调函数,以调用下一个REST,如此下去。这一趋向金字塔厄运,如图1所示。在那里,代码就比它长的垂直速度的水平。回调似乎简单的……直到我们需要一个结果,现在,使用我们接下来的代码。

Pyramid of doom
Pyramid of doom
'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };

function validate() {
   log("Wait for it ...");
   // Sequence of four Long-running async activities
   setTimeout(function () {
      log('result first');
      setTimeout(function () {
         log('result second');
         setTimeout(function () {
            log('result third');
            setTimeout(function () {
               log('result fourth')
            }, 1000);
         }, 1000);
      }, 1000);
   }, 1000);

};
validate();

在上面的代码中,我使用了超时mock异步行为。管理exceptions有可能发挥控制与下游的行为概念是痛苦的。当我们编写Callback,代码的组织就会变得混乱。下面的代码显示了一个模拟的验证流程,将运行,当粘贴到Nodejs REPL。我们将把它从下一段中,一个顺序的Promise pyramid-of-doom。

'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };

// Asynchronous fn executes a callback result fn
function async(arg, callBack) {
   setTimeout(function(){
      log('result ' + arg);
      callBack();
   }, 1000);
};

function validate() {
   log("Wait for it ...");
   // Sequence of four Long-running async activities
   async('first', function () {
      async('second',function () {
         async('third', function () {
            async('fourth', function () {});
         });
      });
   });
};
validate();

在 NodeJS REPL 执行的结果:

$ node scripts/examp2b.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$

我曾经有一个动态验证情况AngularJS那里的表单值可以是动态强制性的,这取决于对表单值。 REST服务裁定强制每个项目的有效价值。我写的一摞功能基础上被要求值运行调度避免嵌套的回调。调度器将弹出从堆栈的函数并执行它。这个函数的回调会不断调用我的调度员,直到堆栈清空完成。每个Callback会记录任何的从远程验证调用的验证错误。

我认为写的代码是一个反模式。如果我用Angluar的$HTTP调用提供的Promise的选择,我在想整个验证会类似于线性形式——类似于同步编程。平展的Promise的链易读的。.

使用Promises

在下面的代码中,我将我们的验证改成了一个Promise链。代码使用了kew promise库. 用Q同样也可以做到这一点。 来试试吧,首先使用NPM将kew库导入NodeJS,然后加载代码到NodeJS REPL。

'use strict';
var Q = require('kew');
var i = 0;

function log(data) {console.log('%d %s', ++i, data); };

// Asynchronous fn returns a promise
function async(arg) {
    var deferred = Q.defer();
    setTimeout(function () {
        deferred.resolve('result ' + arg);\
    }, 1000);
    return deferred.promise;
};

// Flattened promise chain
function validate() {
    log("Wait for it ...");
    async('first').then(function(resp){
        log(resp);
        return async('second');
    })
    .then(function(resp){
        log(resp);
        return async('third')
    })
    .then(function(resp){
        log(resp);
        return async('fourth');
    })
    .then(function(resp){
        log(resp);
    }).fail(log);
};
validate();

输出同嵌套的回调一样:

$ node scripts/examp2-pflat.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$

该代码是稍微更高,但我认为这是比较容易理解和修改。添加错误处理更合理。在链的末端不叫捕获错误的内链,但我也可以提供一个拒绝处理程序在任何后处理其行动的一种排斥。

并行Promise

考虑一个异步操作进另一个异步动作。让后者包括三个并行异步行为,反过来,进到最后动作。它解决了只有当所有的子请求解决并行。见下面的图,这是受来自于十二个的MongoDB链式操作的启发。一些有资格的去操作并行。我实现的Promsie的流

promise flow
promise flow

Figure 6 shows a code fragment that makes ten literals into a promise of ten parallel promises. The then at the end completes only when all ten children resolve or if any child rejects.

我们将如何在图中执行模型的并行的Promise?关键是有一个全功能的最有希望的Promise库将产生的有很多子promises的父Promise在一个数组中举行。当所有的子Promise解决,父Promise也就解决了。如果一个子Promise拒绝,那么父Promise也将拒绝。 图6显示了一个代码片段,使十个字面值为十个平行的Promise。然后只有当所有十个子解决或任何子Promise拒绝才算完成。

var promiseVals = ['To ', 'be, ', 'or ',
    'not ', 'to ', 'be, ', 'that ',
    'is ', 'the ', 'question.'];

var startParallelActions = function (){
    var promises = [];

    // Make an asynchronous action from each literal
    promiseVals.forEach(function(value){
        promises.push(makeAPromise(value));
    });

    // Consolidate all promises into a promise of promises
    return Q.all(promises);
};

startParallelActions ().then( . . .

The following URL, , targets a JSFiddle that runs 10 parallel promises in a browser, rejecting or resolving at random. The complete code is there for inspection and what-if changes. Rerun until you get an opposite completion. Figure 7 shows the positive result.

代码可以见http://jsfiddle.net/mauget/XKCy2/,在浏览器里运行10个并行的Promise, 解决或者拒绝是随机的。完整的代码进行检查,如果有变化。会一直Rerun直到你得到一个相反的完成。下面是一个运气不错的结果。

ten parallel
ten parallel

产生一个Promise

许多API返回一个Promise有一个then函数–他们是可以then。通常我可以链到thenable函数的结果。另外,$Q,和mpromise,kew库有一个简单的API来创建,拒绝,或解决一个Promise。在参考部分的每个库API文档的链接。我通常不需要构建一个Promise,除本条包装答应无知的文字和超时功能。盾看我创建了Promises的例子。

Promise库配合

大多数JavaScript库互操作的Promise在then层。您可以从一个外部的Promise创建别一个Promise,因为Promise可以用任意值。而这可以通过库来支持then。除了那Promis,他们有不同的功能。如果你需要一个函数库而你的库不包含,你可以把你的Promise与库包装起来,构建成一个有你想要的功能的一个新的Promise。例如,jQuery的Promise是在文献中有时诬蔑。你可以将每个正确工作的Q,$Q,mpromise,或kew,包装起来去在库中操作。

最后

写这篇文章的人一年前犹豫的拥抱的Promise。我只是想得到一份工作。我不想学习一种新的API或机会打破我的代码由于误解的Promise。是我错了!当我下车,一分钱,我轻松地取得了可喜的成果。

在这篇文章中,我已经给了一个Promise,简单的例子,一个Promise链,和一个平行的Promise,Promise。Promise是不难用的。如果我能使用他们,任何人都可以。肉的概念,我鼓励你点击了提供了参考专家写的Promise。从开始的Promise/A 参考,JavaScript的事实上的标准的Promise。

如果你没有直接使用的Promise,给他们一个尝试。解决:你会有一个很好的经验。我的承诺!

相关资料

  • http://wiki.commonjs.org/wiki/Promises/A
  • https://github.com/bellbind/using-promise-q/
  • https://github.com/Medium/kew
  • https://docs.angularjs.org/api/ng/service/$q
  • https://github.com/aheckmann/mpromise
  • http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use
  • https://gist.github.com/domenic/3889970
  • http://sitr.us/2012/07/31/promise-pipelines-in-javascript.html
  • http://dailyjs.com/2014/02/20/promises-in-detail/
  • https://www.promisejs.org/
  • http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/
  • http://www.erights.org/elib/distrib/pipeline.html
  • http://zeroturnaround.com/rebellabs/monadic-futures-in-java8/

关于我

Github: @phodal     微博:@phodal     知乎:@phodal    

微信公众号(Phodal)

围观我的Github Idea墙, 也许,你会遇到心仪的项目

QQ技术交流群: 321689806

新书《全栈应用开发:精益实践》

这不是一本深入前端、后台、运维、设计、分析等各个领域的书籍。本书以实践的方式,将这一系列的领域及理论知识结合到一起,来帮助读者构建全栈Web 开发的知识体系,并辅以精益及敏捷的思想,来一步步开发Web 应用:从创建一个UI 原型到编写出静态的前端页面;从静态的前端页面到带后台的应用,并部署应用;从Web 后台开发API 到开发移动Web 应用。在这个过程中,我们还将介绍一些相辅相成的步骤:使用构建系统来加速Web 应用的开发;为应用添加数据分析工具来改进产品;使用分析工具来改善应用的性能;通过自动化部署来加快上线流程;从而帮助读者开发出一个真正可用的全栈 Web 应用。同时,我们也将帮助读者把这些步骤应用到现有的系统上,改进现有系统的开发流程。

comment

Feeds

RSS / Atom

最近文章

关于作者

Phodal Huang

Developer, Consultant, Writer, Designer

ThoughtWorks 高级咨询师

工程师 / 咨询师 / 作家 / 设计学徒

开源深度爱好者

出版有《前端架构:从入门到微前端》、《自己动手设计物联网》、《全栈应用开发:精益实践》

联系我: h@phodal.com

微信公众号: 与我沟通

标签