为什么 .then() 在使用时不需要 async 关键字(类似于 await)? Javascript 怎么知道这是一个异步操作?
Why doesn't .then() need the async keyword when used (similar to await)? How does Javascript know it's an asynchronous operation?
我开始学习异步Javascript,我真的很困惑。
老实说,async/await 方法对我来说似乎非常合乎逻辑。我们需要让运行时知道我们正在执行异步操作,以便它可以相应地处理它。但是为什么我们不需要在使用 .then() 方法时做同样的事情呢?我的意思是,如果 Javascript 已经能够理解何时处理 promises,就不能 await 像 .then()?
一样在没有异步的情况下使用
更令人困惑的是,我看到人们直接在用 async 关键字声明的函数中使用 .then() 。 async/await 不应该是 .then().catch() 方法的语法糖吗?为什么这些可以组合在一起,尤其是在彼此内部?在 async 函数的结果上使用 .then() 不会那么令人困惑,但在彼此内部让我更难理解这一点。
我真的到处搜索对此的解释,但找不到这个确切问题的答案。我所发现的只是人们说您可以使用这两种方法,因为它们本质上是同一件事,但是当您了解细节时,事情就不是很清楚了。
所以异步函数总是return是一个承诺。在其中,等待总是处理承诺。 .then() 可以链接到 await 函数。 .then() 也可以链接到异步函数的结果。如果我们不想在 await 上使用 try/catch,则与 .catch 方法相同。为什么这么混乱?我们可以在没有 .then() 的情况下处理异步的 return 吗?如果 async/await 真的是 .then() 的语法糖,为什么 .then() 在解析后不总是 return 一个 promise?
如果有人能帮忙解释一下,我将不胜感激。谢谢!
这里有很多问题,但一种思考方式是...Promises 不是异步操作,它们只是一种标准模式,可以帮助用户以可预测的方式处理现有的异步操作。
.then()
没什么特别的。它不是 Javascript 关键字,而且 javascript 无论如何都不需要是 'aware'。
承诺是一种模式。你可以自己从头开始写一个 promise class(我强烈推荐这个)。当您使用 .then()
并向其传递一个函数时,您是在告诉承诺 class:“当承诺解决时,请为我调用此函数”。
所有这些都存在于 async/await 之前。 Async/await 是在之后添加的,以便更容易地使用 promise。
我觉得这个问题 'something is syntactic sugar' 毫无意义。它表明语言功能没有那么有意义,因为在语言功能存在之前可以完成相同的事情。虽然这可能是正确的,但与汇编语言相比,任何编程语言 都是如此。对我来说 'syntax sugar' 的定义几乎可以扩展到任何东西,所以它不是一个有用的名称。
async/await(以及它之前的生成器)为语言添加的是 javascript 函数可以在某些情况下中断并稍后恢复。
最后,.then()
和 .catch()
总是 return 承诺。如果您看到一个 .then()
函数没有,那么它不符合 Promise/A+ 规范。
如果你愿意投入工作来完全理解这一点,我会推荐以下 2 个练习:
- 写下你自己的承诺class
- 使用生成器函数重新实现 async/await(请参阅
co
包,了解在 async/await 登陆之前是如何完成的)
async
/await
的目的是允许以串行方式编写异步代码,这在思维上更容易推理(对于某些人而言)。如果您需要等待异步操作完成才能继续执行其余代码,这很有用。例如,如果您需要将异步操作的结果作为参数传递。
示例 1
function asyncOperation1(n) { return Promise.resolve(n+1); }
function asyncOperation2(n) { return Promise.resolve(n/2); }
function asyncOperation3(n) { return Promise.resolve(n*3); }
function errorHandler(err) { console.error(err); }
function main() {
// flow-control
asyncOperation1(1)
.then(asyncOperation2)
.then(asyncOperation3)
.then(continueAfterAsync)
.catch(errorHandler)
// function wrapper
function continueAfterAsync(result) {
console.log(result);
}
}
main();
使用 async
/await
上面 main
函数的代码可能看起来像
async main() {
try {
console.log(
await asyncOperation3(
await asyncOperation2(
await asyncOperation1(1)
)
)
);
} catch(err) {
errorHandler(err);
}
}
注意我们不需要将异步操作函数重写为async function asyncOperation...
来使用await
,但我们需要将main函数声明为async main
.
哪个更好(?)取决于开发人员的品味和以前的编程语言经验。我看到的好处是您不需要将所有内容都包装到函数中并引入额外的流程控制代码,将这种复杂性留给 JavaScript 编译器。
但是,在某些情况下,当您想要安排一些并行任务而不关心哪个先完成时。这些事情只用 async
/await
就比较难做。
示例 2
function main() {
Promise
.all(
['srv1', 'srv2', 'srv3'].map(
srv => fetch(`${srv}.test.com/status`)
)
])
.then(
responses => responses.some(res => res.status !== 200) ?
console.error('some servers have problems') :
console.log('everything is fine')
)
.catch(err => console.error('some servers are not reachable', err))
}
所以,我们看到 .then()
和 await
有共存的空间。
在某些情况下,函数可能是同步的或异步的,具体取决于业务逻辑(我知道这很丑陋,但在某些情况下这是不可避免的)。现在我们来回答您的主要问题
why don't we need to mark an asynchronous operation with .then() and we have to do it with await
换句话说,为什么我们需要 async
关键字?
示例 3
// without `async`
function checkStatus(srv) {
if (!srv.startsWith('srv')) {
throw new Error('An argument passed to checkStatus should start with "srv"')
}
return fetch(`https://${srv}.test.com/status`);
}
function main() {
// this code will print message
checkStatus('srv1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
// this code will fail with
// Uncaught TypeError: (intermediate value).then is not a function
checkStatus('svr1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
}
但是,如果我们定义 async function checkStatus
,编译器会将运行时错误包装到被拒绝的 promise return 值中,并且 main
函数的两个部分都将起作用。
现在假设 JavaScript 允许编写使用 await
的函数,而无需在它们前面指定 async
。
示例 4(无效 Javascript)
function checkStatus(srv) {
if (cache[srv]) {
data = cache[srv];
} else {
data = (await fetch(`https://${srv}.test.com/status`)).json();
}
data.x.y = 'y';
return data;
}
您希望 checkStatus
到 return 是什么? Promise、原始值或抛出异常(如果 data.x
未定义)?
如果你说 Promise,那么使用这个函数的开发者就很难理解为什么 checkStatus
里面可以写 data.x
而外面需要 (await data).x
.
如果是原始值,整个执行流程变得很麻烦,你不能再依赖 JavaScript 是单线程语言这一事实,没有人可以在其中更改变量的值串行写的两行代码
如您所见,async
/await
是一种语法糖。如果这种语法允许我在早期阶段避免可能的运行时错误并保持语言向后兼容,我很想付出在异步函数前面放置额外 async
的代价。
此外,我建议阅读
的答案
简单的解释一下,async/await是句法糖(节点interpreter/compiler/optimizer会把所有东西都转换成普通的Promise)。
这个功能的目标是让我们的生活更轻松,因为编程的回调 way/style 最终会导致我们犯错误。我们称之为“回调地狱”
因此,我们可以在调用使用 async 关键字修饰的函数时使用 .then(),并且我们可以等待 return Promise 对象的函数。
对于一般的优化来说很重要,我们编写的代码告诉编译器我们在性能方面的意思。想象一下,如果我们所有的代码/代码行/指令都可以同时异步或同步。这会导致计算机性能不佳,因为在运行时检查它的任务非常昂贵。
这就是为什么以高效的方式对我们的代码指令如此重要。
我开始学习异步Javascript,我真的很困惑。
老实说,async/await 方法对我来说似乎非常合乎逻辑。我们需要让运行时知道我们正在执行异步操作,以便它可以相应地处理它。但是为什么我们不需要在使用 .then() 方法时做同样的事情呢?我的意思是,如果 Javascript 已经能够理解何时处理 promises,就不能 await 像 .then()?
一样在没有异步的情况下使用更令人困惑的是,我看到人们直接在用 async 关键字声明的函数中使用 .then() 。 async/await 不应该是 .then().catch() 方法的语法糖吗?为什么这些可以组合在一起,尤其是在彼此内部?在 async 函数的结果上使用 .then() 不会那么令人困惑,但在彼此内部让我更难理解这一点。
我真的到处搜索对此的解释,但找不到这个确切问题的答案。我所发现的只是人们说您可以使用这两种方法,因为它们本质上是同一件事,但是当您了解细节时,事情就不是很清楚了。
所以异步函数总是return是一个承诺。在其中,等待总是处理承诺。 .then() 可以链接到 await 函数。 .then() 也可以链接到异步函数的结果。如果我们不想在 await 上使用 try/catch,则与 .catch 方法相同。为什么这么混乱?我们可以在没有 .then() 的情况下处理异步的 return 吗?如果 async/await 真的是 .then() 的语法糖,为什么 .then() 在解析后不总是 return 一个 promise?
如果有人能帮忙解释一下,我将不胜感激。谢谢!
这里有很多问题,但一种思考方式是...Promises 不是异步操作,它们只是一种标准模式,可以帮助用户以可预测的方式处理现有的异步操作。
.then()
没什么特别的。它不是 Javascript 关键字,而且 javascript 无论如何都不需要是 'aware'。
承诺是一种模式。你可以自己从头开始写一个 promise class(我强烈推荐这个)。当您使用 .then()
并向其传递一个函数时,您是在告诉承诺 class:“当承诺解决时,请为我调用此函数”。
所有这些都存在于 async/await 之前。 Async/await 是在之后添加的,以便更容易地使用 promise。
我觉得这个问题 'something is syntactic sugar' 毫无意义。它表明语言功能没有那么有意义,因为在语言功能存在之前可以完成相同的事情。虽然这可能是正确的,但与汇编语言相比,任何编程语言 都是如此。对我来说 'syntax sugar' 的定义几乎可以扩展到任何东西,所以它不是一个有用的名称。
async/await(以及它之前的生成器)为语言添加的是 javascript 函数可以在某些情况下中断并稍后恢复。
最后,.then()
和 .catch()
总是 return 承诺。如果您看到一个 .then()
函数没有,那么它不符合 Promise/A+ 规范。
如果你愿意投入工作来完全理解这一点,我会推荐以下 2 个练习:
- 写下你自己的承诺class
- 使用生成器函数重新实现 async/await(请参阅
co
包,了解在 async/await 登陆之前是如何完成的)
async
/await
的目的是允许以串行方式编写异步代码,这在思维上更容易推理(对于某些人而言)。如果您需要等待异步操作完成才能继续执行其余代码,这很有用。例如,如果您需要将异步操作的结果作为参数传递。
示例 1
function asyncOperation1(n) { return Promise.resolve(n+1); }
function asyncOperation2(n) { return Promise.resolve(n/2); }
function asyncOperation3(n) { return Promise.resolve(n*3); }
function errorHandler(err) { console.error(err); }
function main() {
// flow-control
asyncOperation1(1)
.then(asyncOperation2)
.then(asyncOperation3)
.then(continueAfterAsync)
.catch(errorHandler)
// function wrapper
function continueAfterAsync(result) {
console.log(result);
}
}
main();
使用 async
/await
上面 main
函数的代码可能看起来像
async main() {
try {
console.log(
await asyncOperation3(
await asyncOperation2(
await asyncOperation1(1)
)
)
);
} catch(err) {
errorHandler(err);
}
}
注意我们不需要将异步操作函数重写为async function asyncOperation...
来使用await
,但我们需要将main函数声明为async main
.
哪个更好(?)取决于开发人员的品味和以前的编程语言经验。我看到的好处是您不需要将所有内容都包装到函数中并引入额外的流程控制代码,将这种复杂性留给 JavaScript 编译器。
但是,在某些情况下,当您想要安排一些并行任务而不关心哪个先完成时。这些事情只用 async
/await
就比较难做。
示例 2
function main() {
Promise
.all(
['srv1', 'srv2', 'srv3'].map(
srv => fetch(`${srv}.test.com/status`)
)
])
.then(
responses => responses.some(res => res.status !== 200) ?
console.error('some servers have problems') :
console.log('everything is fine')
)
.catch(err => console.error('some servers are not reachable', err))
}
所以,我们看到 .then()
和 await
有共存的空间。
在某些情况下,函数可能是同步的或异步的,具体取决于业务逻辑(我知道这很丑陋,但在某些情况下这是不可避免的)。现在我们来回答您的主要问题
why don't we need to mark an asynchronous operation with .then() and we have to do it with await
换句话说,为什么我们需要 async
关键字?
示例 3
// without `async`
function checkStatus(srv) {
if (!srv.startsWith('srv')) {
throw new Error('An argument passed to checkStatus should start with "srv"')
}
return fetch(`https://${srv}.test.com/status`);
}
function main() {
// this code will print message
checkStatus('srv1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
// this code will fail with
// Uncaught TypeError: (intermediate value).then is not a function
checkStatus('svr1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
}
但是,如果我们定义 async function checkStatus
,编译器会将运行时错误包装到被拒绝的 promise return 值中,并且 main
函数的两个部分都将起作用。
现在假设 JavaScript 允许编写使用 await
的函数,而无需在它们前面指定 async
。
示例 4(无效 Javascript)
function checkStatus(srv) {
if (cache[srv]) {
data = cache[srv];
} else {
data = (await fetch(`https://${srv}.test.com/status`)).json();
}
data.x.y = 'y';
return data;
}
您希望 checkStatus
到 return 是什么? Promise、原始值或抛出异常(如果 data.x
未定义)?
如果你说 Promise,那么使用这个函数的开发者就很难理解为什么 checkStatus
里面可以写 data.x
而外面需要 (await data).x
.
如果是原始值,整个执行流程变得很麻烦,你不能再依赖 JavaScript 是单线程语言这一事实,没有人可以在其中更改变量的值串行写的两行代码
如您所见,async
/await
是一种语法糖。如果这种语法允许我在早期阶段避免可能的运行时错误并保持语言向后兼容,我很想付出在异步函数前面放置额外 async
的代价。
此外,我建议阅读
简单的解释一下,async/await是句法糖(节点interpreter/compiler/optimizer会把所有东西都转换成普通的Promise)。 这个功能的目标是让我们的生活更轻松,因为编程的回调 way/style 最终会导致我们犯错误。我们称之为“回调地狱”
因此,我们可以在调用使用 async 关键字修饰的函数时使用 .then(),并且我们可以等待 return Promise 对象的函数。
对于一般的优化来说很重要,我们编写的代码告诉编译器我们在性能方面的意思。想象一下,如果我们所有的代码/代码行/指令都可以同时异步或同步。这会导致计算机性能不佳,因为在运行时检查它的任务非常昂贵。
这就是为什么以高效的方式对我们的代码指令如此重要。