Promise 反模式

Promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel. – Bluebird Wiki: Promise Anti Patterns

Promises 是为了让异步代码也能保持这些同步代码的属性:扁平缩进和单异常管道。

Deferred 反模式

这种反模式中,deferred 对象的创建是没有意义的,反而会增加代码的复杂度。

例如:

//Code copyright by Twisternha http://stackoverflow.com/a/19486699/995876 CC BY-SA 2.5myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {    var getConfigurations = function () {        var deferred = $q.defer();

MotorRestangular.all('Motors').getList().then(function (Motors) {            //Group by Config            var g = _.groupBy(Motors, 'configuration');            //Map values            var mapped = _.map(g, function (m) {                return {                    id: m[0].configuration,                    configuration: m[0].configuration,                    sizes: _.map(m, function (a) {                        return a.sizeMm                    })                }            });            deferred.resolve(mapped);        });        return deferred.promise;    };

return {        config: getConfigurations()    }

});

这里的 deferred 对象并没有什么意义,而且可能在出错的情况下无法捕获。

正确的写法应该为:

myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {    var getConfigurations = function () {        //Just return the promise we already have!        return MotorRestangular.all('Motors').getList().then(function (Motors) {            //Group by Cofig            var g = _.groupBy(Motors, 'configuration');            //Return the mapped array as the value of this promise            return _.map(g, function (m) {                return {                    id: m[0].configuration,                    configuration: m[0].configuration,                    sizes: _.map(m, function (a) {                        return a.sizeMm                    })                }            });        });    };

return {        config: getConfigurations()    }

});

再举一个例子:

function applicationFunction(arg1) {  var deferred = Promise.pending(); // 获取 Q.defer()  libraryFunction(arg1, function(err, value) {    if (err) {      deferred.reject(err);    } else {      deferred.resolve(value);    }  });  return deferred.promise;}

这就像重复造轮子,因为回调 API 的封装应该使用 promise 库的 promisification(promise 化)方法实现:

var applicationFunction = Promise.promisify(libraryFunction);

通用 promise 化可能更快,因为可以借助 Promise 的内部操作,还能处理一些极端情况:例如 libraryFunction 同步抛异常或者用到了多个成功值。

什么时候使用 deferred?

必须用的时候。

当要封装的回调 API 和规范不一致时,例如 setTimeout

// 通过 setTimeout 返回 promisefunction delay(ms) {  var deferred = Promise.pending();  setTimeout(function() {    deferred.resolve();  }, ms);  return deferred.promise;}

.then(success, fail) 反模式

这样使用 .then 就像下面这段代码:

var t0;try {  t0 = doThat();} catch(e) {}

// 这样报错发生时会 catch 不到var staff = JSON.parse(t0);

正常的同步写法是:

try {  var stuff = JSON.parse(doThat());} catch(e) {}

所以正确的 .then 用法应该是:

doThat().then(function(v) {  return JSON.parse(v);}).catch(function(e) {});

嵌套 Promise

例如:

loadSomething().then(function(something) {  loadAnothering().then(function(another) {    DoSomethingOnThem(something, another);  });});

如果只是想对两个 promise 的结果做处理,可以使用 Promise.all 方法:

Promise.all([loadSomething, loadAnothering]).then(function(something, another) {  DoSomethingOnThem(something, another);});

断链

例如:

function anAsyncCall() {  var promise = doSomethingAsync();  promise.then(function() {    somethingComplicated();  });  return promise;}

这里的问题在于加入 somethingComplicated() 出错的话不会被捕获。promise 应该链式调用。也就是说所有的 then 方法都应该返回一个新的 promise。所以上面代码的正确写法为:

function anAsyncCall() {  var promise = doSomethingAsync();  return promise.then(function() {    somethingComplicated();  });}

集合

例如需要对一个集合中的每个元素执行异步操作:

function workMyCollection(arr) {  var resultArr = [];  function _recursive(idx) {    if (idx >= resultArr.length) return resultArr;    return doSomethingAsync(arr[idx]).then(function(res) {      resultArr.push(res);      return _recursive(idx + 1);    });  }  return _recursive(0);}

这里的问题在于需要遍历数组,其实可以用 promise.all 解决:

function workMyCollection(arr) {  return q.all(    arr.map(function(item) {      return doSomethingAsync(item);    })  );    }

总结

最容易犯的错误,没有使用 catch 去捕获 then 里抛出的报错:

// snippet1somePromise().then(function () {  throw new Error('oh noes');}).catch(function (err) {  // I caught your error! :)});

// snippet2somePromise().then(function resolve() {  throw new Error('oh noes');}, function reject(err) {  // I didn't catch your error! :(});

这里的问题在于 snippet2 在 function resolve 中出错时无法捕获。而 catch 则可以。

下面的两个示例返回结果是不一样的:

// example1Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {  console.log(result);  // foo});// example2Promise.resolve('foo').then(function () {  return Promise.resolve('bar')}).then(function (result) {  console.log(result); // bar});

example2 改变了返回值,因而 result 发生了变化。

更多关键词

async flow control, event loop, callback, promises, generators, async/wait, coroutines

Promises 所做的承诺是保证异步代码顺序执行,并能够链式管理异常和错误。相比使用event loop 和回调(callback)来控制异步代码的顺序执行,Promises 能够让代码更加清晰易懂。generator 更是从语言级别上提供了更好的支持。

V8 优化

V8 有两个编译器:通用编译器和优化编译器。也就是V8 中的 JavaScript 代码总是被编译成机器码后才执行的。

例如 a + b 编译成汇编代码为:

mov eax, amov ebx, bcall RuntimeAdd

但如果 a 和 b 都是整数的话,则会被编译成:

mov eax, amov ebx, badd eax, ebx

这样编译后的代码性能会快很多,因为跳过了 JavaScript 对不同类型数据相加的处理。

通用编译器会得到前一种汇编码,优化编译器会得到后一种汇编码。两种汇编代码性能很容易产生 100 倍的差异。但是存在一些模式,这些模式下的代码优化编译器不会去处理(称为bail out)。Promises 属于一种被 bail out 的模式。

他山之石

Python greenlets(基于gevent)

go coroutine

参考资料

转载自:http://taobaofed.org/blog/2016/05/03/promise-anti-patterns/

作者:云翮

时间: 2024-11-02 16:23:31

Promise 反模式的相关文章

Java中的常量:如何避免反模式

在应用中,我们往往需要一个常量文件,用于存储被多个地方引用的共享常量.在设计应用时,我也遇到了类似的情况,很多地方都需要各种各样的常量. 我确定需要一个单独的文件来存储这些静态公共常量.但是我不是特别确定是应该用接口还是类(枚举不满足我的需求).我有两种选择: 使用接口,如: package one; public interface Constants { String NAME="name1"; int MAX_VAL=25; } 或 package two; public cla

Java 理论与实践: 伪typedef反模式

将泛型添加到 Java 语言中增加了类型系统的复杂性,提高了许多变量和方法声明的冗长程度.因为没有提供 "typedef" 工具来定义类型的简短名称,所以有些开发人员转而把扩展当作 "穷人的 typedef",结果收到了良好的效果. 对于 Java 5.0 中新增的泛型工具,一个常见的抱怨就是,它使代码变得太冗长.原来用一行就够的变量声明不再存在了,与声明参数化类型有关的重复非常讨厌,特别是还没有良好地支持自动补足的 IDE.例如,如果想声明一个 Map,它的键是

浅谈现代企业中性能分析的反模式

什么是性能分析 "性能分析"一词有许多种定义,但在我看来最有用的一个是: 一种由测量驱动的方法,用以了解一个应用程序在负载下的行为. 这个定义的好处是,它提醒您注意测量是整个过程的关键点.并通过简单的延伸,也提醒您统计和数据分析可能是性能工程师的重要工具. 进一步讲,它使我们更相信应把性能分析看作是一项基础的实证研究活动,是它把输入和输出粘合在一起组成实验科学. 这样,这些输出就可以被框定为一系列具有量化答案的问题,比如: 如果有10倍的客户数,系统还有足够的内存来应付吗? 在客户看来

解析SOA反模式

了解不同的面向服务的体系结构 (SOA) 反模式,这些反模式对通常出现的会产生确定性负面结果的情形或解决方案进行了描述.随着越来越多的企业开始大举从 Web 服务转向 SOA,引入.采用和成功实现 SOA 方面的各种障碍变得越来越明显.其中一些障碍与导致过去的关键活动失败的因素类似;而其他障碍则是 SOA 特有的.对这些障碍和最差实践进行记录,将帮助顾问.架构师和专业人员不再犯同样的错误,并学习如何避免这些问题的发生.此处汇集和说明的反模式是由作者通过作为 IBM 架构师的个人经验.研究过去和当

JUnit反模式

JUnit 的出现为开发人员带来了福音.遗憾的是,许多人仍然认为学会 JUnit API,编写几个测试,最后得到一个测试良好的应用程序就足够了.这种想法比不进行任何测试还要糟,因为这会导致对代码健康状态的误解.学习 JUnit 是测试中最容易的一部分.编写优秀的测试则是较困难的一个环节.本文将介绍一些常见的 JUnit 反模式,并说明如何解决它们. 两个月前,我和妻子决定在厨房里装上木镶板.这是我第一次装修房子,我带着一股盲目乐观主义精神,使用铁锤和钉子干起了装修.但这样做几乎是一场灾难,因为我

反模式读书笔记之实现主体架构(二)

1引言 有一名专业的规划师(Jack)说过,一名工程师的20%时间应该用于做规划.随着我们经验的增加,对这一论断的相信程度也在增加.通过规划来很好的组织工作,生产率和效率都会得到极大的提高.不幸的是很多公司机构都试图把过多的规划活动形式化.规划在由个人来推动和利用时最有效,时间管理专家的一个减少压力的关键要素就是通过规划让生活中的各项活动保持均衡.随着这种实践活动的成熟,时间国立系统的形式和使用方法越来越个人化了. 2实现主体架构 本反模式的特点是开发中的系统缺乏架构规范.一般负责项目的架构师都

反模式读书笔记之胖球(—)

1.胖球产生的原因: 胖球反模式本身是很简单,但可能由于疏忽,后期没加以控制,系统急于上线等等原因而出现了. 胖球反模式通过描述一个或几个类不断的膨胀,以至吞食掉整个面向对象架构.一般胖球的出现是由于一个类垄断了处理过程,而其他的类只是数据的封装体. 虽然OOA&D 提出了很久,但有些人的思维还停留在过程式的设计上,他们习惯把过程和数据分开,而不是OO中把融合了方法和数据的对象进行职责分割.胖球可能是需求分析不当的结果,也可能是系统不断演进,迭代,新功能和新人员的加入而使部分构件异常庞大而没有进

绑定子类的泛型基类,反模式?

 这次总结一个个人认为的反模式:"绑定子类的泛型层基类",这个模式在一些著名的框架中也见到过,如果CSLA.BlogEngine.我自己在原来的写的框架中,也用到过.     当然了,个人认为是反模式,各们同仁并不一定这样认为,仁者见仁,智者见智了.不过我好几次都是受尽折磨,所以决定写出来给大家分享下心得.   模式介绍     "层基类"是MF提出的一个基本模式,详见:<Layer Supertype>.这种模式在经典的层次型架构设计的实现中,是极其重

[译] JavaScript 的函数式编程是一种反模式

本文讲的是[译] JavaScript 的函数式编程是一种反模式, 原文地址:Functional programming in JavaScript is an antipattern 原文作者:Alex Dixon 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:sunui 校对者:LeviDing.xekri 其实 Clojure 更简单些 写了几个月 Clojure 之后我再次开始写 JavaScript.就在我试着写一些很普通的东西的时候,我