作者:Alan Storm

翻译:疯狂的技术宅

原文:https://alanstorm.com/what-is...

未经容许严禁转载

当我第一次看到 async/await 的形容时有点困惑,直到我理解了 async/await 的出处,才领悟到它的价值。本文咱们来探讨 javascript 世界中最先进的异步编程思维之一。

什么是异步函数

javascript 中的异步函数是有着一些“超能力”的惯例函数。要定义异步函数,须要在其定义前加上 async 关键字。

// File: hello-async.js// const notAnAsyncFunction = function() {//    console.log("Hello Not Async")// }const helloAsync = async function() {    console.log("Hello Async")}// 如果用 javascript 的箭头函数语法// const helloAsync = async () => {//    console.log("Hello Async")//}helloAsync()

运行下面的程序,失去上面的输入。

$ node hello-async.jsHello Async

在取得超能力之前,应该先明确异步函数不是什么。默认状况下异步函数不会异步运行你的代码。看上面的代码。

// File: hello-async.jsconst helloAsync = async () => {    /*2.*/ console.log("Hello Async")}/*1.*/ console.log("Before Function Call")helloAsync()/*3.*/ console.log("After Function Call")

如果运行下面的程序,将会失去以下输入。

$ node hello-async.jsBefore Function CallHello AsyncAfter Function Call

咱们能够看到程序是按程序输入的,即 helloAsync 中的工作不是异步实现的。

你可能还留神到了代码中带编号的正文( /*1.*/)本文中的这类正文能够帮你了解代码的执行程序。

永远返回 Promise

当初咱们晓得异步函数不是什么,然而它们是什么呢?异步函数的第一个超级性能:总是返回一个 promise。看上面的代码

// File: hello-async.jsconst helloWorld = async function() {    return "Hello World"}console.log(helloWorld())

咱们心愿这个小程序输入字符串 Hello World。然而,如果运行它:

$ node hello-async.jsPromise { 'Hello World' }

能够看到它返回一个实例化的 Promise 对象,并且该对象曾经确定为咱们返回的值(字符串 Hello World)。异步函数获取了咱们返回的字符串,并将其转换为一个 Promise。

如果想在程序中实现这个 Promise 的值,就须要应用 then 办法.

// File: hello-async.jsconst helloWorld = async function() {    return "Hello World"}let promise = helloWorld()promise.then(function(result){    console.log(result)})

如果你从异步函数返回显式的 Promise,它将会变得更加简单。比咱们想要的简单得多。简短的版本是 async 函数返回新的 Promise,这个 Promise 最终将解析为你返回的 Promise 的已解决值。如果运行这段代码

// File: hello-async.jsconst helloWorld = async function() {    const promise = new Promise(function(resolve) {        setTimeout(function(){            resolve("Hello Promise")        })    })    return promise}const promise = helloWorld()console.log(promise)promise.then(function(result) {    console.log("] " + result)})

你将失去一个 Promise,Promise 的值将变为 Hello Promise

$ node hello-async.jsPromise { <pending> }] Hello Promise

有意思的是,如果你用 then 办法返回任何对象的话,

// File: hello-async.jsconst helloWorld = async function() {    const promise = {        then: function() {        }    }    return promise}const pendingPromise = helloWorld()console.log(pendingPromise)

异步函数会将其视为 promise。

$ node hello-async.jsPromise { <pending> }

这些对象有时称为“then-able”。因而,你能够将任意第三方 Promise 库与异步函数一起应用(只有该库应用 then 办法即可)。

Await

异步函数总是返回 promise。要晓得为什么总是返回 Promise,就须要理解异步函数的第二种超级性能:应用 await 语句实现 promise 的能力。

在异步函数中编写代码时,能够应用 await 语句。该语句在异步函数内部不可用。

$ node> const promise = new Promise(function(){})undefined> await promiseawait promise^^^^^SyntaxError: await is only valid in async function

await 语句冀望在其右侧有一个 promise 对象。当你应用 await 语句时,javascript 会暂停 async 函数的执行,期待 promise 返回一个值,而后继续执行。

看上面的程序:

// File: hello-async.jsconst createPromise = function(message) {    const promise = new Promise(function(resolve, reject){        setTimeout(function(){            if(message) {                /*1.*/ resolve(message)            } else {                reject("No Message Provided")            }        }, 0)    })    return promise}const promise = createPromise("Hello Promise")const main = function(promise) {    /*2.*/ console.log("Starting Main")    promise.then(function(result){        /*4.*/ console.log(result)    })    /*3.*/ console.log("Ending Main")}main(promise)

这是另一个用来演示证实 promise 如何运作的小程序。该程序创立一个Promise对象,该对象将异步返回 Hello Promise 文本。而后,在名为 main 的函数中,咱们要求该 promise 作为其值,而后记录这个值。执行程序失去以下输入。

$ node hello-async.jsStarting MainEnding MainHello Promise

也就是说,在 promise 中的异步工作运行之前,main 函数将实现执行。Promise 自身迫使咱们应用回调来获取Promise 的值。咱们无奈在 main 的作用域内取得 Promise 的值。

上面是雷同的程序,然而用了 async 函数和 await 语句。

// File: hello-async.jsconst createPromise = function(message) {    const promise = new Promise(function(resolve, reject){        setTimeout(function(){            if(message) {                /*2.*/ resolve(message)            } else {                reject("No Message Provided")            }        }, 0)    })    return promise}const promise = createPromise("Hello Promise")const main = async function(promise) {    /*1.*/ console.log("Starting Main")    const message = await promise    /*3.*/ console.log(message)    /*4.*/ console.log("Ending Main")}main(promise)

运行程序失去以下输入:

$ node hello-async.jsStarting MainHello PromiseEnding Main

也就是说 main 函数将会输入第一行

// File: hello-async.jsconsole.log("Starting Main")

而后当咱们应用 await 时,该函数将会进行并期待 promise 执行,而后输入其后果

// File: hello-async.jsconst message = await promiseconsole.log(message)

最初输入最初一条日志。

// File: hello-async.jsconsole.log("Ending Main")

换句话说,即便咱们用的是 Promise,此函数中代码也能够使咱们以同步的形式执行。

这就是 async/await 的力量。 原始连续传递格调异步API创立了 “callback-heck”,每个异步上下文具备多层嵌套回调。 Promise 对此进行了一些清理,使咱们能够将事件限度在单个级别的回调中。 async/await 组合是它的下一个改良——应用 await,咱们能够无需应用回调就可能失去异步工作的后果。

异步的对立面?

async/await 组合是弱小的,然而可能有些货色会困扰你。

而后当咱们应用 await 时,该函数将进行”并期待 promise 执行,而后输入其后果

如果 await 期待执行实现,那么这个异步代码会怎么?这听起来像异步代码的“对立面”。

依据后面曾经通知你的所有,你的顾虑是正确的。然而咱们依然没有探讨异函数能如何与你程序的其余部分进行交互。

当你的异步函数正在期待 promise 解决时,javascript 将跳回到调用上下文并继续执行主程序。如果咱们用以下内容替换 main 函数及其调用,会失去什么后果呢?

// File: hello-async.js/* ... */const main = async function(promise) {    console.log("Starting Main")    const message = await promise    console.log(message)    console.log("Ending Main")}console.log("Before Main")main(promise)console.log("After Main")

如果运行 下面这段程序,则会失去以下输入。

$ nodeBefore MainStarting MainAfter MainHello PromiseEnding Main

也就是说序将会依照以下程序执行

const main = async function(promise) {    /* 3.*/ console.log("Starting Main")    /* 4.*/ const message = await promise    /* 6.*/ console.log(message)    /* 7.*/ console.log("Ending Main")}/* 1.*/ console.log("Before Main")/* 2.*/ main(promise)/* 5.*/ console.log("After Main")

尽管代码在 main 中按程序执行,然而调用 main 则意味着整个程序不会按程序执行。相同,执行将来到 main 函数,在 main 作用域内实现执行,最初返回实现 main 的执行。

返回两次?

该执行模型存在一些歧义。例如上面这段程序的异步函数

// File: hello-async.jsconst createPromise = function(message) {    const promise = new Promise(function(resolve, reject){        setTimeout(function(){            if(message) {                /*1.*/ resolve(message)            } else {                reject("No Message Provided")            }        }, 0)    })    return promise}const someFunction = async function() {    const promise = createPromise("Hello Async")    const toReturn = await promise    return "The Promise Returned: " + toReturn}result = someFunction()console.log(result)

如果 await 使函数提前返回,那么 result 中会是什么?不会是 "The Promise Returned: " + toReturn,因为该代码尚未运行。让咱们运行程序并找出答案。

$ node hello-async.jsPromise { <pending> }

这就是为什么异步函数总是返回 promise 的起因。当咱们应用 await 时,程序无奈晓得该函数的理论返回值。而是函数调用返回一个 Promise。那么是否须要异步函数返回的值?你能够应用 promise 的 then 办法,或者如果你在另一个异步函数中,则能够用 await 返回的 promise。

总结

我花了一段时间才理解 async/await 是如何对异步事务状态进行整体改良的。只管 async/await 的确简化了应用 promise 的各个函数的执行过程,但你依然必须解决越来的越非线性代码。

我意识到,async/await 在零碎程序员和客户端程序员之间提供了清晰明了的界线。客户端程序员能够像执行同步工作一样应用 promise,然而在幕后并没有真正阻止 promise。零碎程序员承当治理整个程序的异步复杂性的累赘。

如果你考虑一下这几天有多少零碎在工作,这是有情理的。在像 express 之类的路由框架中,一般的客户端程序员实际上并没有真正思考 express 外部的工作,他们只是编写路由函数。在反对 async/await 的路由框架中,客户端程序员能够编写其路由并 await ...,直到他们称心为止。express 外围团队将须要解决这些路由函数所返回的 promise。

async/await 模式使服务端开发人员有机会从客户端代码中“打消”异步编程的复杂性。尽管还处于初期,但随着更多框架开始思考应用 await 来构建零碎,对于尚未具备编写异步代码的深层背景的程序员,古代 javascript 的可拜访性将有所提高。


本文首发微信公众号:前端先锋

欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章

欢送持续浏览本专栏其它高赞文章:

  • 深刻了解Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13个帮你进步开发效率的古代CSS框架
  • 疾速上手BootstrapVue
  • JavaScript引擎是如何工作的?从调用栈到Promise你须要晓得的所有
  • WebSocket实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30分钟用Node.js构建一个API服务器
  • Javascript的对象拷贝
  • 程序员30岁前月薪达不到30K,该何去何从
  • 14个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把HTML转成PDF的4个计划及实现

  • 更多文章...