[英]Why does this async/await function return a promise?
I'm not sure why the following function returns a promise, when I'm using the async
/ await
operators?当我使用
async
/ await
运算符时,我不确定为什么以下 function 返回 promise?
getBase64 = async (url) => {
const response = await axios.get(url, {
responseType: 'arraybuffer'
})
const buffer = new Buffer.from(response.data,'binary').toString('base64')
return ('data:image/jpeg;base64,' + buffer)
}
I know I can simply add .then(data => console.log(data))
but I want to assign the raw data to a variable, like:我知道我可以简单地添加
.then(data => console.log(data))
但我想将原始数据分配给一个变量,例如:
const base64Img = getBase64()
...which is not possible since the return type is a promise for some reason. ...这是不可能的,因为由于某种原因,返回类型是 promise。
An async function is just a promise.异步 function 只是 promise。 If you want the value returned, you will need to await it as well.
如果你想要返回值,你也需要等待它。
const bas64Img = await getBase64()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
The short and admittedly not very telling answer to why async
functions always return a promise, is because the ECMAScript specification, which defines how async
functions behave, mandates so .关于为什么
async
函数总是返回 promise 的简短且公认不是很能说明问题的答案是因为定义async
函数行为方式的 ECMAScript 规范要求如此。
It is the rationale behind their choices I will attempt to explain, but I can tell you already that it is related to some other relevant parts of the language specification such as execution contexts , jobs and job queues .我将尝试解释他们选择背后的基本原理,但我已经可以告诉您,它与语言规范的其他一些相关部分有关,例如执行上下文、作业和作业队列。
Those specifications were put in place partly due to historical reasons -- ECMAScript originating as JavaScript in Web browsers and the way these went about allowing scripting of Web pages.这些规范的实施部分是由于历史原因——ECMAScript 在 Web 浏览器中起源为 JavaScript 以及这些规范允许 ZC6E190B284633C48E39E55049DA3CCEZ 页面编写脚本的方式。 Since the language was standardised after multiple implementations had been in place for a while, ECMAScript was defined retroactively, in part:
由于该语言在多个实现已经到位一段时间后被标准化,ECMAScript 是追溯定义的,部分原因是:
The fifth edition of ECMAScript (published as ECMA-262 5th edition) codified de facto interpretations of the language specification that have become common among browser implementations [...]
ECMAScript 第五版(以 ECMA-262 第 5 版出版)编纂了已在浏览器实现中普遍使用的语言规范的事实上的解释 [...]
(from the 7th paragraph of "Introduction" ) (摘自“引言”第7段)
In any case, crucially, ECMAScript does not allow parallel script execution -- a JavaScript function call, for example, will never be interrupted by any other function call, period.在任何情况下,至关重要的是,ECMAScript 不允许并行脚本执行——例如,JavaScript function 调用永远不会被任何其他 ZC1C425268E68385D1AB5074C17A94F1 调用中断。 Not within the same realm (in Web browsers one realm is associated with one single rendered Web page).
不在同一个realm内(在 Web 浏览器中,一个 realm 与一个呈现的 ZC6E190B28404933C48E3953E 页面相关联)。
We can dig deeper into the "why" if we imagine a JavaScript runtime tasked with calling an async
function, and see what it would take for the runtime to arrange for returning the actual value, when the value would depend on an asynchronous operation.如果我们想象一个 JavaScript 运行时负责调用
async
function,我们可以更深入地挖掘“为什么”,并看看运行时安排返回实际值需要什么,而该值将取决于异步操作。 So let's use your getBase64
function as an example.因此,让我们以您的
getBase64
function 为例。 Now, we would obviously be violating the language specification doing this, but it's just to arrive at an explanation.现在,我们这样做显然违反了语言规范,但这只是为了得到一个解释。
Looking inside the body of getBase64
, let's assume that axios.get(...)
is a potentially lengthy operation because it fetches a resource on the network.查看
getBase64
的内部,假设axios.get(...)
可能是一个冗长的操作,因为它从网络上获取资源。 If we assume it isn't, getBase64
does not have to be async
, cannot use await
, and behaves like a regular function.如果我们假设它不是,
getBase64
不必是async
,不能使用await
,并且表现得像一个普通的 function。 So, for the sake of this answer, we do assume it involves a lengthy operation, and we do not have the value for response
before this lengthy operation completes.所以,为了这个答案,我们假设它涉及一个冗长的操作,并且在这个冗长的操作完成之前我们没有
response
的价值。 axios.get
may be fetching a resource over network, but the nature of the operation doesn't really matter, what matters is that the operation may potentially take longer time than completing execution of the entire script otherwise would. axios.get
可能正在通过网络获取资源,但操作的性质并不重要,重要的是该操作可能比完成整个脚本的执行需要更长的时间,否则会花费更长的时间。
So, can we continue executing the body of getBase64
and return before axios.get(...)
completes?那么,我们可以继续执行
getBase64
的主体并在axios.get(...)
完成之前返回吗? For the sake of the argument, we could execute some parts of it that do not depend on the promise returned by axios.get(...)
being resolved.为了论证,我们可以执行它的某些部分,这些部分不依赖于
axios.get(...)
返回的 promise 被解析。 That's called automatic code parallelisation.这称为自动代码并行化。 ECMAScript does not define that and may not even allow it.
ECMAScript 没有定义它,甚至可能不允许它。 Few languages do.
很少有语言可以。 But as I mentioned, it is otherwise technically possible to evaluate those statements in the function body that do not depend on the resolved promise, but in any case, the return value of
getBase64
depends on response
, indirectly, so we at least need the lengthy operation to complete before returning.但正如我所提到的,在技术上可以评估 function 主体中不依赖于解析的 promise 的那些语句,但无论如何,
getBase64
的返回值间接依赖于response
,所以我们至少需要冗长的返回前完成操作。 That's the important thing.这才是最重要的。
So if we cannot return yet, we have to wait for the promise returned by axios.get(...)
to resolve.因此,如果我们还不能返回,我们必须等待
axios.get(...)
返回的 promise 解决。 I mean, one way or another we need to have response
before returning a value from getBase64
.我的意思是,在从
getBase64
返回值之前,我们需要以一种或另一种方式response
。
Here is where it gets interesting.这就是有趣的地方。 A scripting runtime always has an environment it actually scripts.
脚本运行时始终具有它实际编写脚本的环境。 Part of this environment may be the browser window (
window
), a Web page ( document
) or something else entirely.此环境的一部分可能是浏览器 window (
window
)、Web 页面 ( document
) 或其他完全。 As a notable example of a compliant ECMAScript runtime, Node.js has an environment, too.作为兼容 ECMAScript 运行时的一个显着示例,Node.js 也有一个环境。 A question then comes to mind -- what to do if an event needs to be dispatched by the environment while
getBase64
is waiting for the axios.get(...)
to resolve?然后想到一个问题——如果在
getBase64
正在等待axios.get(...)
解决时,环境需要调度事件该怎么办? There may be an incoming network request (Node.js) or user clicking on a link (Web browser).可能有传入的网络请求(Node.js)或用户单击链接(Web 浏览器)。 You have an event listener attached, too (wouldn't be much point in having events you can't react to, would it?).
您也附加了一个事件侦听器(让您无法响应的事件没有多大意义,不是吗?)。 Now, do you call and start executing the event listener while the
getBase64
call is waiting for response
, or do you wait for execution of getBase64
and the rest of the script (think of the call chain) to complete, risking poor user experience for having user wait for potentially "long" time (the network is "slow", for example)?现在,您是在
getBase64
调用等待response
时调用并开始执行事件侦听器,还是等待脚本的getBase64
和 rest 的执行(想想调用链)完成,冒着糟糕的用户体验的风险用户等待潜在的“长”时间(例如,网络“慢”)?
If you start executing the event handler, you invariably end up with two script execution contexts in parallel -- the event handler execution and the getBase64
call waiting for axios.get(...)
to resolve.如果您开始执行事件处理程序,您总是会得到两个并行的脚本执行上下文——事件处理程序执行和等待
axios.get(...)
解决的getBase64
调用。 In effect, your event handler is executing while getBase64
is still in progress, even if the agent only uses one single thread of execution where the former pre-empts the latter.实际上,您的事件处理程序在
getBase64
仍在进行时正在执行,即使代理仅使用一个执行线程,而前者先于后者。 It's still parallel script execution.它仍然是并行脚本执行。 This is no small matter -- behaviour of your script, in particular the order of statements being executed, now depends on the event timing, You will have to deal with potential race conditions.
这不是小事——脚本的行为,尤其是执行语句的顺序,现在取决于事件时间,您将不得不处理潜在的竞争条件。 and state synchronisation problems in general.
和 state 同步问题一般。
If you do not allow parallel script execution, you have to serialise it.如果不允许并行脚本执行,则必须对其进行序列化。 Meaning finishing one script execution -- completing the
getBase64
call and the rest of the script that ended up calling it, for instance -- and only then starting another, like execution of an event handler.意味着完成一个脚本执行——例如,完成
getBase64
调用和最终调用它的脚本的 rest——然后才开始另一个,比如执行事件处理程序。
This is what ECMAScript designers went for.这就是 ECMAScript 设计师所追求的。 They decided to forbid parallel script execution in return for the simplicity this gets developers by freeing them from having to deal with an entire class of parallel programming problems briefly mentioned above.
他们决定禁止并行脚本执行,以换取开发人员的简单性,让他们不必处理上面简要提到的整个 class 并行编程问题。 This constrains runtime developers in a way where different compliant runtimes executing same script will evaluate the statements present in the script in the same order, and not depending on event timing, for one.
这限制了运行时开发人员,其中执行相同脚本的不同兼容运行时将以相同顺序评估脚本中存在的语句,而不是依赖于事件时间。
Some may call this overspecification of the language, but I believe I have made some sort of a case for it.有些人可能会称之为语言的过度规范,但我相信我已经为它做了某种论证。 In any case, JavaScript had much more modest ambitions before it was standardised, and relative ease of scripting attainable by avoiding situations that stem from parallel script execution, and a simpler Web browser architecture as a result, in a time when no one spoke of CPU "cores", were priorities.
在任何情况下,JavaScript 在标准化之前都有更温和的野心,通过避免并行脚本执行产生的情况,脚本编写相对容易,结果是更简单的 Web 浏览器架构,在没有人谈论 CPU 的时代“核心”是优先事项。 ECMAScript standardised that behaviour with a rigidly specified model.
ECMAScript 使用严格指定的 model 标准化该行为。
This of course still leaves us with a problem of "freezing" pages when script execution takes too long to complete, since executing event handlers and anything affecting the environment exposed to scripts, must be delayed until current execution completes.当脚本执行需要很长时间才能完成时,这当然仍然会给我们留下“冻结”页面的问题,因为执行事件处理程序和任何影响脚本暴露环境的东西都必须延迟到当前执行完成。 And if you ever tried to calculate anything complex enough in your page scripts, you know what I mean.
如果你曾经尝试在你的页面脚本中计算任何足够复杂的东西,你就会明白我的意思。 No two script executions happen in parallel.
没有两个脚本执行并行发生。 Execution of callbacks for intervals, timers, promises and other operations are queued and done in their own execution contexts, as is script execution.
间隔、计时器、承诺和其他操作的回调执行在它们自己的执行上下文中排队并完成,脚本执行也是如此。 These and other jobs -- actual progression of network requests and operations initiated by different kind of APIs -- use ECMAScript job queues.
这些和其他作业——由不同类型的 API 发起的网络请求和操作的实际进程——使用 ECMAScript 作业队列。 On top of that, HTML specifies something called event loops which also use job queues internally.
最重要的是,HTML 指定了称为事件循环的东西,它也在内部使用作业队列。 These specifications rigidly define how events are handled, when callbacks are fired by different APIs, and otherwise still allow the scripting environment to stay responsive between hopefully short-lived script executions.
这些规范严格定义了事件的处理方式、回调何时由不同的 API 触发,否则仍然允许脚本环境在希望短暂的脚本执行之间保持响应。
In actuality, async
function execution, as you may have discerned from the "AsyncFunctionStart" definition of the specification , is split into multiple jobs at well-defined points.实际上,
async
function 执行,正如您可能已经从规范的“AsyncFunctionStart”定义中看出的那样,在明确定义的点被拆分为多个作业。 True to the specification, there is no parallel script execution going on, yet the function still effectively "waits" with every await
expression.按照规范,没有并行脚本执行,但 function 仍然有效地“等待”每个
await
表达式。
So in conclusion yes, we technically could have async
functions always return the values you tell them to return with the return
keyword, but you would be trading in great deal of simplicity of JavaScript development in return for a different kind of "simplicity", allowing interruptions of async
functions (interleaving execution contexts in general) in order to mask callback/promise handling and be able to directly work with return values, but needing to watch state changes more closely and ever being wary of the runtime peculiarities.所以总而言之,是的,从技术上讲,我们可以让
async
函数始终返回您告诉它们返回的值,使用return
关键字,但是您会以 JavaScript 开发的大量简单性换取另一种“简单性”,允许async
函数的中断(通常是交错执行上下文)以屏蔽回调/承诺处理并能够直接使用返回值,但需要更密切地观察 state 的变化,并时刻警惕运行时特性。 The ECMAScript designers made the choice for you. ECMAScript 设计师为您做出了选择。
async
functions return "actual" values?async
函数不能返回“实际”值? Even without allowing of parallel script execution, technically nothing would stop an async
function that doesn't use await
from returning the value you intend it to.即使不允许并行脚本执行,从技术上讲,没有什么可以阻止不使用
await
的async
function 返回您想要的值。 Such function would be able to execute to completion without waiting on any promise.这样的 function 将能够执行完成,而无需等待任何 promise。 Basically it then behaves as an ordinary function, being
async
"in name only".基本上它的行为就像一个普通的 function,是
async
的“仅在名称上”。 But you'd be violating ECMAScript specification which mandates that async
functions always return a promise.但是您将违反 ECMAScript 规范,该规范要求
async
函数始终返回 promise。 Again, a decision by designers of the ECMAScript specification.再次,由 ECMAScript 规范的设计者决定。
The reason behind their decision isn't known to me, but I dare argue it is so that one wouldn't have to look at the implementation of an async
function ("are there any await
keywords here?") in order to know how to use it -- one can use the same kind of code (like using await
) for calling the function and handling the result, simply knowing if it's async
or not.我不知道他们决定背后的原因,但我敢说这是为了让人们不必查看
async
function 的实现(“这里有await
关键字吗?”)以了解如何使用它——可以使用相同类型的代码(如使用await
)来调用 function 并处理结果,只需知道它是否是async
的。 If it is, you'd be using await foo()
or foo().then(result => {... })
where foo
is such async
function, and it would always work no matter if execution of foo
itself includes await
or not ( async function foo() { return 1; }
).如果是,您将使用
await foo()
或foo().then(result => {... })
其中foo
是这样的async
function,并且无论foo
本身的执行是否包含await
,它都将始终有效与否( async function foo() { return 1; }
)。 You wouldn't need to look at its implementation.你不需要看它的实现。 I think this is called a "calling contract".
我认为这被称为“调用合同”。
await
without async
?async
就不能使用await
? In a similar manner to the rationale explained above, the decision to mandate functions be marked as async
in order to be able to use await
is, again, a choice on part of ECMAScript designers.与上面解释的基本原理类似,为了能够使用
await
,强制将函数标记为async
的决定再次是 ECMAScript 设计人员的选择。 ECMAScript could allow any function to use await
but then, because the language does not allow parallel script execution, any such function would need to return a promise lest it severely impact due event handling. ECMAScript 可以允许任何 function 使用
await
但随后,由于该语言不允许并行脚本执行,任何此类 function 都需要返回 promise 以免严重影响事件处理。 Assuming the former and staying with this thought, either all functions would then be returning promises, or only those that use await
would be.假设前者并坚持这个想法,那么要么所有函数都将返回 Promise,要么只有那些使用
await
的函数才会返回。 All JavaScript functions always returning promises would probably be considered a terrible idea by at least ECMAScript designers, and the sentiment you yourself express with your question would seem to share their opinion.至少 ECMAScript 设计人员可能会认为所有JavaScript 函数总是返回承诺可能会被认为是一个糟糕的想法,并且您自己对问题表达的观点似乎与他们的观点相同。 Having only those functions that use
await
always return promises, while a better idea, will imply that the type of value being returned by a function varies with the function's implementation , something that sort of flies in the face of the principle of "calling contracts", again -- you would need to know whether foo
uses await
in order to know how to work with the result of calling this foo
.只有那些使用
await
的函数总是返回 Promise,虽然这是一个更好的主意,但这意味着 function 返回的值类型随函数的实现而变化,这与“调用合约”的原则背道而驰,再次 - 您需要知道foo
是否使用await
才能知道如何处理调用此foo
的结果。 ECMAScript designers opted against it, expectedly, in my opinion, and this is why functions that use await
must be explicitly marked -- with async
.在我看来,ECMAScript 设计者选择了反对它,这就是为什么必须明确标记使用
await
的函数 - 使用async
。
Also, both specifications arguably help make for more optimized language runtimes.此外,这两个规范都可以说有助于实现更优化的语言运行时。
Because it gives you a choice what to do.因为它让你可以选择做什么。 The code below async function invocation can be run immediately or it can wait till the async finishes.
异步 function 调用下面的代码可以立即运行,也可以等到异步完成。
You can even have some code that depends on async result to wait for a function to complete and some other code that doesn't care about the result run immediately:您甚至可以让一些依赖于异步结果的代码等待 function 完成,而其他一些不关心结果的代码会立即运行:
someAsyncFunction().then(v => {
// here goes the code that depends on v
}
// here goes the code that doesn't care about async completion
In blocking languages that behave just how you want it you can't do this.在阻止行为正是你想要的语言时,你不能这样做。 The rest of the code must always wait.
代码的 rest 必须始终等待。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.