内容简介:CertSimple 网站最近发布了一篇文章,说 ES2017 里的 async 和 await 是 JS 最好的特性。我非常赞同。基本上来说,JS 为数不多的几个优点之一就是对异步请求的处理得当。这得益于它从 Scheme 那里继承来的函数和闭包。
CertSimple 网站最近发布了一篇文章,说 ES2017 里的 async 和 await 是 JS 最好的特性。我非常赞同。
基本上来说,JS 为数不多的几个优点之一就是对异步请求的处理得当。这得益于它从 Scheme 那里继承来的函数和闭包。
然而这也是 JS 的最大的问题之一,因为这导致了回调地狱(callback hell),这个看起来无法回避的问题导致异步的 JS 代码可读性非常差。为了解决回调地狱,大家尝试了很多方案,但大都失败了。Promise 方案差点解决了这个问题,但还是失败了。
最终,我们看到了 async/await 与 Promise 联合的方案,这个方案非常好地解决了问题。在这篇文章里,我将解释为什么会这样,以及 Promise、async/await 和 do 语法、monad 之间的关系。
首先,我们尝试用三种不同风格的代码来获取读取用户所有账户里的余额。(一个用户有多个账户 accout,每个账户里都有余额 balence)
错误的方案:回调地狱
function getBalances(callback) {
api.getAccounts(function (err, accounts) { // 回调
if (err) {
callback(err);
} else {
var balances = {}; // 余额
var balancesCount = 0;
accounts.forEach(function(account, i) {
api.getBalance(function (err, balance) { // 回调
if (err) {
callback(err);
} else {
balances[account] = balance;
if (++balancesCount === accounts.length) {
callback(null, balances);
}
}
});
});
}
});
};
复制代码
这是一种很容易想到的方法,但是它有两层回调,这份代码丑陋中有 3 个问题需要解决:
- 每一个地方都要对 err 进行了处理
- 用计数器来计算异步得来的值
- 不可避免的嵌套
几乎正确的方案:Promise
function getBalances() {
return api.getAccounts()
.then(accounts =>
Promise.all(accounts.map(api.getBalance))
.then(balances => Ramda.zipObject(accounts, balances))
);
}
复制代码
这个代码解决了上面的三个问题:
- 我们可以在最后一个 then 里统一处理 error
- Promise.all 使得我们不需要定义额外的计数器
- 我们可以最大程度地避免嵌套
但是还有一个问题没有解决,那就是 then 还是嵌套了,第二个 then 在第一个 then 的回调里,因为第二个 then 需要用到第一个then 的 accounts 变量。所以对代码进行正确的缩进非常重要。
不过解决方法也是有的,那就是让第一个 then 把 accounts 传给第二个 then:
function getBalances() {
return api.getAccounts()
.then(accounts => Promise.all(accounts.map(api.getBalance)
.then(balances => [accounts, balances])))
.then(([accounts, balances]) => Ramda.zipObject(accounts, balances));
}
复制代码
但是这样会导致又多了一个 then。可以看到 Promise 基本上解决了回调低于,但是并没有完全解决。
正确的方案:async/await
async function getBalances() {
const accounts = await api.getAccounts();
const balances = await Promise.all(accounts.map(api.getBalance));
return Ramda.zipObject(balances, accounts);
}
复制代码
async 函数里可以出现 await 关键字,await 会得到 Promise 对象完成任务,然后再执行下一句话。
有了这些我们就不用再蛋疼地缩进了。这是如何做到的呢?我们需要追根溯源。
回调地狱的起源
很多人都认为回调地狱只有在异步任务中才有,实际上只要我们用回调来处理被包裹的值,就会出现回调地狱。
假设你想打印出 [1,2,3] [4,5,6] [7,8,9]
的所有排列组合,比如 [1,4,7] [1,4,8]
等等:
[1,2,3].map((x) => {
[4,5,6].map((y) => {
[7,8,9].map((z) => {
console.log(x,y,z);
})
})
});
复制代码
看,我们熟悉的回调地狱出现了。这是完全同步的代码,但是 async 和 await 只能处理异步……
假设我们为同步代码也创建类似的关键字叫做 multi/pick,那么上面的代码就可以写成
multi function () {
x = pick [1, 2, 3];
y = pick [4, 5, 6];
z = pick [7, 8, 9];
console.log(x, y, z);
}
复制代码
当然,这个语法是不存在的。
Monad 和 do
语法有些语言拥有一些特性能处理所有的这类需求,并且不区分异步还是同步。
译注:中间的过程需要一些 TS 和 Haskell 知识,能看懂的请自行阅读。代码是大概是这样的:
getBalances :: Promise (Map String String) -- 这是类型声明 getBalances = do accounts <- getAccounts balances <- getBalance accounts return (Map.fromList (zip accounts balances)) 复制代码
这个语法叫做 do 标记或者 do 语法。它要求 Promise 满足 Monad 的一些规则。
do 语法和 Monad 是在 1995 年被用在 Haskell 里的(译注:JS 在 2015 年,也就是 20 年后才把 Promise 引入)。
这两个特性从此解决了回调地狱。如果把 JS 的 Promise、await/async 与 Haskell 的 Monad、do 语法做对比的话,你会发现
await/async 之于 Promise,正如 do 语法之于 Monad
既然 Haskell 上已经验证了 Monad 能够有效避免回调地狱,那么 JS 就可以直接放心用 await 了。
总结
回调地狱没了,JS is great again。但是为什么花了这么久时间 JS 才去借鉴 Monad 呢?要是 2013 年,社区里的人听从了 『那个疯狂的家伙』的建议 就好了。
全文完。
译注:那个疯狂的家伙说了什么呢?打开链接你可以看到一个 GitHub Issues 页面,那个家伙的名字叫做 Brian Mckenna(布莱恩)。
布莱恩提议使用函数式编程的方案来优化 Promise。
然而提案的维护者 domenic 却并不领情。
domenic 说
我们不会这样做的。这种方案不切实际,为了满足某些人自己的审美偏好创造出了奇怪而又无用的 API,无法应用在 JS 里。你没有理解 Promise 要解决的问题是在命令式编程语言里提供异步流程控制模型。 这种方案是非常不严密的(hilariously inaccurate),因为没有满足我们的 spec,应该只能通过我们 1/500 的测试用例。
这个回复得到了 16 赞和 254 个踩。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 译文 | 推荐信:程序排错
- Protobuf -java基础教程(译文)
- 译文: Basics of Futexes
- 跨站请求伪造已经死了!(译文)
- (译文)通过一个例子理解paxos算法
- iOS·UIView Apple 官方文档译文
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Chinese Authoritarianism in the Information Age
Routledge / 2018-2-13 / GBP 115.00
This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!