有问题?
我的问题是我的 catch 里面有一个 Promise,这意味着我有嵌套。现在,我强烈反对 promise 的嵌套,所以我真的很想摆脱它,但我就是不知道怎么做。
– Flame_Phoenix
问题是你认为你有问题——或者你在 StackOverflow 上而不是 CodeReview 上发布了这个问题。 article you linked 显示了您对嵌套 Promise 的幼稚看法
你会得到一整套相互嵌套的承诺:
loadSomething().then(function(something) {
loadAnotherthing().then(function(another) {
DoSomethingOnThem(something, another);
});
});
你这样做的原因是因为你需要对两个 promise 的结果做一些事情,所以你不能把它们链接起来,因为 then() 只传递了前一个 return 的结果。
您这样做的真正原因是您不了解Promise.all() 方法。
– 代码猴子,http://taoofcode.net
不,Promise.all 只能有时替换嵌套的 Promise。一个简单的反例——这里,一个promise的值依赖于另一个,所以两个必须被排序
getAuthorByUsername (username) .then (a => getArticlesByAuthorId (a.id))
嵌套 promise 并不是总是必要的,而是将其称为“反模式”并鼓励人们在他们知道差异有害之前避免它,imo。
语句不起作用
other linked article 显示您可能再次被误导的地方
不要误会我的意思,async/await 并不是世界上万恶之源。在使用它几个月后,我实际上学会了喜欢它。因此,如果您对编写命令式代码感到满意,那么学习如何使用 async/await 来管理您的异步操作可能是一个不错的举措。
但是,如果您喜欢 Promise,并且喜欢学习并将越来越多的函数式编程原则应用到您的代码中,您可能希望完全跳过 async/await 代码,停止思考命令式并转向这种新旧范式。
——加布里埃尔·蒙特斯
只有这没有任何意义。如果您查看 JavaScript 中的所有命令式关键字,您会注意到它们都没有计算为值。为了说明我的意思,请考虑
let total = if (taxIncluded) { total } else { total + (total * tax) }
// SyntaxError: expected expression, got keyword 'if'
或者如果我们尝试在另一个表达式的中间使用if
makeUser (if (person.name.length === 0) { "anonymous" } else { person.name })
// SyntaxError: expected expression, got keyword 'if'
这是因为if 是一个语句,它永远不会计算出一个值——相反,它只能依赖副作用。
if (person.name.length === 0)
makeUser ("anonymous") // <-- side effect
else
makeUser (person.name) // <-- side effect
低于for 永远不会计算出一个值。相反,它依赖于副作用来计算sum
let sum = 0
let numbers = [ 1, 2, 3 ]
for (let n of numbers)
sum = sum + n // <-- side effect
console.log (sum) // 6
do、while、switch 甚至 return 以及所有其他命令式关键字也是如此——它们都是语句,因此依赖于副作用计算值。
那么什么会计算出一个值呢? 表达式求值
1 // => 1
5 + 5 // => 10
person.name // => "bobby"
person.name + person.name // => "bobbybobby"
toUpper (person.name) // => "BOBBY"
people .map (p => p.name) // => [ "bobby", "alice" ]
async 和 await 不是陈述
您可以将异步函数分配给变量
const f = async x => ...
或者您可以将异步函数作为参数传递
someFunc (async x => ... )
即使 async 函数什么也不返回,async 仍然保证我们会收到一个 Promise 值
const f = async () => {}
f () .then (() => console.log ("done"))
// "done"
您可以await 一个值并将其分配给一个变量
const items = await getItems () // [ ... ]
或者你可以await另一个表达式中的值
items .concat (await getMoreItems ()) // [ ... ]
因为async/await 形成了表达式,所以它们可以与函数式风格一起使用。如果你想学习函数式风格并避免async 和await,那只是因为你被误导了。如果 async 和 await 只是命令式风格,那么这样的事情永远不可能发生
const asyncUnfold = async (f, initState) =>
f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
, async () => []
, initState
)
实例
这是一个实际示例,我们有一个记录数据库,我们希望执行递归查找或其他操作...
const data =
{ 0 : [ 1, 2, 3 ]
, 1 : [ 11, 12, 13 ]
, 2 : [ 21, 22, 23 ]
, 3 : [ 31, 32, 33 ]
, 11 : [ 111, 112, 113 ]
, 33 : [ 333 ]
, 333 : [ 3333 ]
}
一个异步函数Db.getChildren 位于您和您的数据之间。如何查询一个节点及其所有的后代?
const Empty =
Symbol ()
const traverse = (id) =>
asyncUnfold
( async (next, done, [ id = Empty, ...rest ]) =>
id === Empty
? done ()
: next (id, [ ...await Db.getChildren (id), ...rest ])
, [ id ]
)
traverse (0)
// => Promise [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]
用蒙特斯的话来说,是从“JavaScript 开发者的天堂”送来的纯程序。它是使用函数表达式编写的,错误会相应地冒出,我们甚至不必碰.then。
我们可以使用命令式风格编写相同的程序。或者我们也可以使用.then 来编写函数式风格。我们可以用各种方式编写它,我想这就是重点——感谢 async 和 await 形成表达式的能力,我们可以在各种风格中使用它们,包括函数式风格。
在下面的浏览器中运行整个程序
const asyncUnfold = async (f, init) =>
f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
, async () => []
, init
)
const Db =
{ getChildren : (id) =>
new Promise (r => setTimeout (r, 100, data [id] || []))
}
const Empty =
Symbol ()
const traverse = (id) =>
asyncUnfold
( async (next, done, [ id = Empty, ...rest ]) =>
id === Empty
? done ()
: next (id, [ ...await Db.getChildren (id), ...rest ])
, [ id ]
)
const data =
{ 0 : [ 1, 2, 3 ]
, 1 : [ 11, 12, 13 ]
, 2 : [ 21, 22, 23 ]
, 3 : [ 31, 32, 33 ]
, 11 : [ 111, 112, 113 ]
, 33 : [ 333 ]
, 333 : [ 3333 ]
}
traverse (0) .then (console.log, console.error)
// => Promise
// ~2 seconds later
// [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]