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.5
myApp.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 返回 promise
function 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 里抛出的报错:

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

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

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

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

// example1
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
console.log(result); // foo
});
// example2
Promise.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, a
mov ebx, b
call RuntimeAdd

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

mov eax, a
mov ebx, b
add eax, ebx

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

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

他山之石

Python greenlets(基于gevent)

go coroutine

参考资料

转载自:http://taobaofed.org/blog/2016/05/03/promise-anti-patterns/
作者:云翮
目录
相关文章
|
6月前
|
前端开发 Go
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
【5月更文挑战第3天】Go语言通过goroutines和channels实现异步编程,虽无内置Future/Promise,但可借助其特性模拟。本文探讨了如何使用channel实现Future模式,提供了异步获取URL内容长度的示例,并警示了Channel泄漏、错误处理和并发控制等常见问题。为避免这些问题,建议显式关闭channel、使用context.Context、并发控制机制及有效传播错误。理解并应用这些技巧能提升Go语言异步编程的效率和健壮性。
340 5
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
|
6月前
|
前端开发 JavaScript Java
每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)
每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)
206 0
每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)
|
前端开发 JavaScript 容器
JS 中Promise 模式
异步模式在web编程中变得越来越重要,对于web主流语言Javscript来说,这种模式实现起来不是很利索,为此,许多Javascript库(比如 jQuery和Dojo)添加了一种称为promise的抽象(有时也称之为deferred)。
857 0
|
6月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
65 1
|
6月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
99 4
|
6月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
5月前
|
前端开发 JavaScript 开发者
JavaScript进阶-Promise与异步编程
【6月更文挑战第20天】JavaScript的Promise简化了异步操作,从ES6开始成为标准。Promise有三种状态:pending、fulfilled和rejected。基本用法涉及构造函数和`.then`处理结果,如: ```javascript new Promise((resolve, reject) => { setTimeout(resolve, 2000, '成功'); }).then(console.log); // 输出: 成功
89 4
|
6月前
|
JSON 前端开发 JavaScript
【JavaScript技术专栏】JavaScript异步编程:Promise、async/await解析
【4月更文挑战第30天】JavaScript中的异步编程通过Promise和async/await来解决回调地狱问题。Promise代表可能完成或拒绝的异步操作,有pending、fulfilled和rejected三种状态。它支持链式调用和Promise.all()、Promise.race()等方法。async/await是ES8引入的语法糖,允许异步代码以同步风格编写,提高可读性和可维护性。两者结合使用能更高效地处理非阻塞操作。
95 0
|
4月前
|
前端开发 JavaScript
JavaScript异步编程:Promise与async/await的深入探索
【7月更文挑战第9天】Promise和async/await是JavaScript中处理异步编程的两大利器。Promise为异步操作提供了统一的接口和链式调用的能力,而async/await则在此基础上进一步简化了异步代码的书写和阅读。掌握它们,将使我们能够更加高效地编写出清晰、健壮的异步JavaScript代码。
|
4月前
|
前端开发 JavaScript 定位技术
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
JavaScript 等待异步请求数据返回值后,继续执行代码 —— async await Promise的使用方法
59 1

热门文章

最新文章