简体   繁体   English

为什么这个异步/等待 function 返回 promise?

[英]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 设计师为您做出了选择。

Why can't some 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.即使不允许并行脚本执行,从技术上讲,没有什么可以阻止不使用awaitasync 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".我认为这被称为“调用合同”。

Why can't one use 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.此外,这两个规范都可以说有助于实现更优化的语言运行时。


1 I use "ECMAScript" and "JavaScript" interchangeably -- discussing the distinction, if any, is beyond the scope of the answer and should in any case have little bearing on the argument. 1我交替使用“ECMAScript”和“JavaScript”——讨论区别(如果有的话)超出了答案的 scope 并且在任何情况下都应该与论点无关。

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.

相关问题 为什么 Axios(带有 async/await)会返回一个完整的 promise? - Why does Axios (with async/await) return a full promise? function 真的需要返回 promise 才能让异步等待工作吗? - Does a function really need to return promise for async-await to work? 为什么异步和等待未在函数外部返回已解决的承诺? - Why does async and await not returned a resolved promise outside of the function? 承诺-使用异步/等待功能将数据返回常量 - Promise - return data to constant with async / await function 返回不等待异步函数 - return does not await async function 为什么函数 getSafestCountriesNames() 在我调用它时返回 promise{pending} 而当我使用 async/await 时它返回 undefined? - Why does the function getSafestCountriesNames() return promise{pending} when i call it and when i use async/await it returns undefined? 异步函数中的等待函数是否必须返回一个promise? (的node.js) - Does an await-ed function inside an async function have to return a promise? (node.js) 为什么我的异步 function 总是返回待处理的 promise? - Why does my async function always return a pending promise? Promise 嵌套在异步 function 中 - 为什么不只使用异步/等待? - Promise nested inside async function - why not working only with async/await? 为什么异步等待 function 返回一个值,而 promise function 返回一个未定义? - Why does async await function returns a value while promise function returns an undefined?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM