为什么异步代码如此令人困惑(以及如何使其变得简单)强制同步的肮脏技巧 问题不在于异步代码 从一开始就异步编写 如果没有区别会怎样?异步函数组合 ESNext 提案:管道运算符 MojiScript MojiScript 异步示例 我需要你的帮助!总结

2025-05-25

为什么异步代码如此令人困惑(以及如何使其变得简单)

强制同步的肮脏黑客

问题不在于异步代码

从一开始就异步编写

如果没有区别怎么办?

异步函数组合

ESNext 提案:管道操作符

MojiScript

MojiScript 异步示例

我需要你的帮助!

概括

纱

为什么 JavaScript 中的异步代码如此复杂和令人困惑?有很多文章和问题,都是人们试图用 Bean 来解决这个问题的。

从 SO 中精心挑选的一些问题...

关于异步的问题和文章有数百个,其中很多听起来像这样:

// How do I do this in JavaScript?
action1();
sleep(1000);
action2();
Enter fullscreen mode Exit fullscreen mode

这是对 JavaScript 工作原理的一个常见误解。

强制同步的肮脏黑客

甚至有肮脏的黑客可以强制同步

不推荐

问题不在于异步代码

我花了很多时间思考 JavaScript,其中一次我突然冒出一个愚蠢的想法。如果问题不是出在异步代码上呢?如果问题实际上是出在同步代码上呢?

同步代码有问题吗?怎么回事?

我经常先同步写代码,然后再尝试异步处理。这是我的错误。

异步代码无法在同步环境中运行。但是,反之则没有问题。

异步/异步功能图表

此限制仅适用于同步代码!

震惊的 GIF

从一开始就异步编写

意识到这一点后,我现在知道我应该异步开始我的代码。

因此,如果我要再次解决异步问题,我会像这样开始:

Promise.resolve()
    .then(() => action1())
    .then(() => sleep(1000))
    .then(() => action2())
Enter fullscreen mode Exit fullscreen mode

或与asyncawait...

const main = async () => {
  action1()
  await sleep(1000)
  action2()
}
Enter fullscreen mode Exit fullscreen mode

这个Promise解决方案……有点啰嗦。async/await更好一些,但它只是 Promise 链的语法糖。我还得加上asyncawait,希望我能搞定。

有时async/await可能会令人困惑。例如:这两行代码的作用完全不同。

// await `action()`
await thing().action()

// await `thing()`
(await thing()).action()
Enter fullscreen mode Exit fullscreen mode

以下是Burke Holland最近发表的一篇文章:

[DEV.to] Async/Await 和 forEach 的绝望深渊

如果没有区别怎么办?

所以我开始重新思考……如果异步代码和同步代码之间没有区别会怎样?如果我可以编写代码而不必担心我编写的代码是否是异步的会怎样?如果异步和同步语法完全相同会怎样?这可能吗?

嗯,这意味着我不能使用标准函数,因为它们只是同步的。async/await也不行。那段代码完全不一样,而且它本身就很复杂。而且 Promise 要求我到处都写then, then, then……

因此,我又开始思考……

异步函数组合

我超爱函数式编程。所以,我开始思考异步函数组合,以及如何将它应用到这个问题上。

如果您是第一次听说函数组合,这里有一些代码可能会对您有所帮助。这是一个典型的(同步)“hello world”函数组合。如果您想了解更多关于函数组合的知识,请阅读这篇文章:函数式 JavaScript:日常使用的函数组合

const greet = name => `Hello ${name}`
const exclaim = line => `${line}!`

// Without function composition
const sayHello = name =>
  exclaim(greet(name))

// With function composition (Ramda)
const sayHello = pipe(
  greet,
  exclaim
)
Enter fullscreen mode Exit fullscreen mode

这里我使用和来pipe组合成一个新的函数greetexclaimsayHello

既然pipe它只是一个函数,我可以修改它使其也能异步工作。这样一来,代码是同步还是异步就无关紧要了。

我要做的一件事是将任何回调函数转换为 Promise 函数。幸好 Node 的内置功能util.promisify让这变得简单。

import fs from 'fs'
import { promisify } from 'util'
import pipe from 'mojiscript/core/pipe'

// Synchronous file reader
const readFileSync = fs.readFileSync

// Asynchronous file reader
const readFile = promisify(fs.readFile)
Enter fullscreen mode Exit fullscreen mode

现在,如果我将同步示例与异步示例进行比较,则没有区别

const syncMain = pipe([
  file => readFileSync(file, 'utf8'),
  console.log
])

const asyncMain = pipe([
  file => readFile(file, 'utf8'),
  console.log
])
Enter fullscreen mode Exit fullscreen mode

这正是我想要的!!!

即使readFileSync是同步和readFile异步,语法也是完全相同的,输出也完全相同!

我不再需要关心什么是同步,什么是异步。我在两种情况下编写的代码都是一样的。

香蕉说“呜呜呜!”

ESNext 提案:管道操作符

值得一提的是ESNext 提案:管道运算符

建议的管道操作符将允许您以同样的方式“管道化”功能pipe

// pipeline
const result = message =>
  message
    |> doubleSay
    |> capitalize
    |> exclaim

// pipe
const result = pipe([
  doubleSay,
  capitalize,
  exclaim
])
Enter fullscreen mode Exit fullscreen mode

Pipeline Operator之间的格式pipe非常相似,我也可以毫无问题地在两者之间切换。

管道提案非常令人兴奋,但也有两个注意事项。

  1. 它还没有到来,我不知道它是否会到来,或者什么时候到来,或者它会是什么样子。babel 是一个选择。
  2. 它(目前)还不支持await,当它支持时,很可能需要不同的语法来管道同步和异步函数。真恶心。

pipe与管道运算符语法相比,我仍然更喜欢函数语法。

再次,管道将同步启动代码,我已经将其确定为一个问题。

所以,虽然我对这个功能很兴奋,但我可能永远不会用它,因为我已经有更好的了。这让我百感交集 :|

MojiScript

这就是你问我这到底是什么......

import pipe from 'mojiscript/core/pipe'
//                ----------
//               /
//          WAT?
Enter fullscreen mode Exit fullscreen mode

(好吧,你没有问……但你仍在阅读,而我还在写……)

MojiScript是一种异步优先、固执己见的函数式语言,旨在与 JavaScript 引擎 100% 兼容。

由于 MojiScript 是异步优先的,因此异步代码不会像典型的 JavaScript 那样带来问题。事实上,用 MojiScript 编写异步代码是一种乐趣。

您还可以将 MojiScript 中的函数导入到现有的 JavaScript 应用程序中。了解更多信息:https://github.com/joelnet/MojiScript

MojiScript 异步示例

这是另一个使用 MojiScript 异步的优秀示例pipe。此函数提示用户输入,然后使用Axios搜索星球大战 API,然后将格式化的结果写入控制台。

const main = ({ axios, askQuestion, log }) => pipe ([
  askQuestion ('Search for Star Wars Character: '),
  ifEmpty (showNoSearch) (searchForPerson (axios)),
  log
])
Enter fullscreen mode Exit fullscreen mode

如果这让你感到好奇,请在此处查看完整的源代码:https://github.com/joelnet/MojiScript/tree/master/examples/star-wars-console

我需要你的帮助!

现在轮到我向你求助了。MojiScript 就像一个超级全新的、处于 pre-alpha 阶段且实验性的项目,我正在寻找贡献者。你能做些什么贡献呢?你可以试用一下,提交 Pull Request,给我反馈,或者问我问题,任何方式都可以!快去https://github.com/joelnet/MojiScript查看吧。

概括

  • 异步代码无法在同步环境中运行。
  • 同步代码在异步环境中也能正常运行。
  • 从一开始就异步编写代码。
  • for循环是同步的。删除它们。
  • 尝试使用类似这样的异步函数组合pipe
  • pipe具有与 ESNext Pipeline Proposal 类似的功能,但现已可用。
  • 使用MojiScript玩转:)
  • MojiScript 目前处于实验阶段,因此请不要将其投入生产!

MojiScript 入门:FizzBu​​zz(第 1 部分)

在DEV.toMedium上阅读我的更多文章

在 Twitter 上关注我@joelnet

干杯!

文章来源:https://dev.to/joelnet/why-async-code-is-so-damn-confusing-and-a-how-to-make-it-easy-3441
PREV
迷你指南 - 与 MySQL 一起构建 REST API 作为 Go 微服务设置 API 数据库连接结构和依赖关系数据库迁移包装 REST API
NEXT
React:“我真希望我能用这种方式编写组件。” DevTwitter Chrome 扩展程序 🍻 🍻 🍻