发布于 2026-01-06 2 阅读
0

setTimeout 是一个回调函数。如果我们修改它会发生什么?Node 风格的回调函数、Promis 化 setTimeout、使用 sleep、异步函数组合、异步 map、for 循环中的 setTimeout、相关搜索、总结、DEV 全球展示挑战赛,由 Mux 呈现:展示你的项目!

setTimeout 是一个回调函数。如果我们修改它会发生什么?

Node 风格的回调函数

承诺设置超时

利用睡眠

异步函数组合

异步映射

在 for 循环中使用 setTimeout

谷歌搜索相关内容

概括

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

金色钟表

如今,将 Node 风格的回调函数转换为 Promise 风格的函数已成为一种常见的做法。那么,为什么我们没有对 也这样做呢setTimeout

比起 Node 风格的回调函数,使用 Promise 风格的函数的主要原因是避免回调地狱

Ryo 在回调地狱中发射火球代码

谁都不想看到那种场面。

查看setTimeout(及其兄弟函数setIntervalsetImmediate),我可以清楚地看出这是一个回调式函数。

setTimeout(callback, 1000);
//         --------
//                 \
//                   See that? Right there. A callback!
Enter fullscreen mode Exit fullscreen mode

setTimeout然而,将回调函数转换为 Promise 的做法却极其罕见。这种做法是如何setTimeout悄无声息地被人们接受的?它与其他方法有何setTimeout不同,以至于能够被认可?

我说不。

Node 风格的回调函数

setTimeout可能被忽略的原因是,尽管它显然是一个回调风格的函数,但它不是节点风格的回调函数,两者略有不同。

首先,让我们看一下 node 风格的回调函数,以便更好地了解它们之间的区别。fs.readFile这是一个 node 风格回调函数的绝佳示例。

fs.readFile(path[, options], callback)
//                           --------
//                          /
//    callback must be last
Enter fullscreen mode Exit fullscreen mode

回调函数本身必须如下所示:

const callback = (err, data) => { /* ... */ }
//                ---  ----
//               /          \
//    error first             data last
Enter fullscreen mode Exit fullscreen mode

如果setTimeout它是一个传统的 Node 风格的回调函数,那么可以使用 Node 的 `return` 函数轻松将其转换为Promise 风格util.promisify的函数。以下示例展示了如何使用 `return` 函数轻松地将其util.promisify转换fs.readFile为 Promise 风格的函数。

import fs from 'fs'
import { promisify } from 'util'

const readFile = promisify(fs.readFile)
Enter fullscreen mode Exit fullscreen mode

很遗憾,util.promisify这样做行不通。首先,因为回调函数不是最后一个参数。其次,因为回调函数不符合接口(err, data)规范。

承诺设置超时

幸运的是,手动转换同样简单。我将把这个新函数命名为sleep……

const sleep = milliseconds => value => new Promise (resolve =>
  setTimeout(() => resolve(value), milliseconds)
)
Enter fullscreen mode Exit fullscreen mode

关于这段代码,我想指出几点关键事项。

  • sleep是咖喱味的。你稍后就会明白为什么了。
  • sleep接受一个参数value,然后解析该参数value。稍后您就会明白原因。

利用睡眠

现在,在代码中添加暂停就像使用 Promise 一样简单。

const log => msg => console.log(msg)

sleep(1000)('Hello World').then(log)
Enter fullscreen mode Exit fullscreen mode

这当然很好,但这并不是我写这篇文章的原因。

真正让我兴奋的sleep是,它可以被巧妙地插入到承诺链的中间。

在这个例子中,在 API 调用之间添加 1 秒延迟非常简单。

import axios from 'axios'
import sleep from 'mojiscript/threading/sleep'

const fetchJson = url => axios.get(url).then(response => response.data)
const log = msg => (console.log(msg), msg)
//                                  -
//                                 /
//     comma operator. google it.

fetchJson('https://swapi.co/api/people/1')
  .then(log)
  .then(sleep(1000))
  .then(() => fetchJson('https://swapi.co/api/people/2'))
  .then(log)
  .then(sleep(1000))
  .then(() => fetchJson('https://swapi.co/api/people/3'))
  .then(log)
Enter fullscreen mode Exit fullscreen mode

因为sleep它接受一个输入值并返回相同的值,所以它会将该值传递给下一个 Promise。sleep本质上,它变成了 Promise 链中间件。

让我们看看用 async/await 风格编写这段代码:

import axios from 'axios'
import sleep from 'mojiscript/threading/sleep'

const fetchJson = url => axios.get(url).then(response => response.data)
const log = msg => (console.log(msg), msg)

const main = async () => {
  const people1 = await fetchJson('https://swapi.co/api/people/1')
  log(people1)
  await sleep(1000)
  const people2 = await fetchJson('https://swapi.co/api/people/2')
  log(people2)
  await sleep(1000)
  const people3 = await fetchJson('https://swapi.co/api/people/3')
  log(people3)
}

main()
Enter fullscreen mode Exit fullscreen mode

说实话,我喜欢这些问题的sleep解决方法,但我不太喜欢我刚才演示的这两个代码的语法。在这两个例子中,我甚至觉得async/await语法更糟糕。代码await到处都是乱码,很容易出错。

异步函数组合

函数组合非常强大,可能需要阅读很多文章才能完全理解。不仅要了解它的用法,还要了解它的原理。如果你想入门,我建议从这里开始:《函数式 JavaScript:日常使用的函数组合》

本文特意不解释函数组合。我相信我即将展示的语法非常简单,你完全不需要理解函数组合。

import axios from 'axios'
import pipe from 'mojiscript/core/pipe'
import sleep from 'mojiscript/threading/sleep'

const fetchJson = url => axios.get(url).then(response => response.data)
const log = msg => (console.log(msg), msg)

const main = pipe ([
  () => fetchJson('https://swapi.co/api/people/1'),
  log,
  sleep(1000),
  () => fetchJson('https://swapi.co/api/people/2'),
  log,
  sleep(1000),
  () => fetchJson('https://swapi.co/api/people/3'),
  log
])

main()
Enter fullscreen mode Exit fullscreen mode

哇,这代码真漂亮!

但既然我们已经在讨论函数组合了,那么很容易将fetchJson, log,sleep提取出来pipe,使代码更加 DRY 一些。

import axios from 'axios'
import pipe from 'mojiscript/core/pipe'
import sleep from 'mojiscript/threading/sleep'

const fetchJson = url => axios.get(url).then(response => response.data)
const log = msg => (console.log(msg), msg)

const fetchLogWait = pipe ([
  id => fetchJson (`https://swapi.co/api/people/${id}`),
  log,
  sleep(1000)
])

const main = pipe ([
  () => fetchLogWait (1),
  () => fetchLogWait (2),
  () => fetchLogWait (3)
])

main()
Enter fullscreen mode Exit fullscreen mode

异步映射

MojiScript 还具有独特的异步映射功能。(敬请期待近期发布的专题文章。)

异步映射是我决定使用MojiScriptpipe不是Ramda来编写这些示例的原因pipeP到目前为止,这些示例使用 Ramda 也能正常运行pipeP。从现在开始,这些示例将完全基于 MojiScript。

map让我们来看一些代码!异步执行Ajax 调用是不是很容易?

const main = pipe ([
  ({ start, end }) => range (start) (end + 1),
  map (fetchLogWait),
])

main ({ start: 1, end: 3 })
Enter fullscreen mode Exit fullscreen mode

简直太简单了!

文本

所有内容都整合到一个可运行的代码块中:

import axios from 'axios'
import log from 'mojiscript/console/log'
import pipe from 'mojiscript/core/pipe'
import map from 'mojiscript/list/map'
import range from 'mojiscript/list/range'
import sleep from 'mojiscript/threading/sleep'

const fetchJson = pipe ([
  axios.get,
  response => response.data
]) 

const fetchLogWait = pipe ([
  id => fetchJson (`https://swapi.co/api/people/${id}`),
  log,
  sleep (1000)
])

const main = pipe ([
  ({ start, end }) => range (start) (end + 1),
  map(fetchLogWait),
])

main ({ start: 1, end: 3 })
Enter fullscreen mode Exit fullscreen mode

这段代码已经非常符合 DRY 原则了!

在 for 循环中使用 setTimeout

如果你还没遇到过这个问题,它经常出现在 JavaScript 面试中。这段代码运行结果与预期不符。请问输出结果是什么?

for (var i = 1; i < 6; i++) {
  setTimeout(() => console.log(i), 1000)
}
Enter fullscreen mode Exit fullscreen mode

如果你没猜到它会暂停 1 秒钟,然后6一次性打印五个 ',那你就错了。

同样的程序,使用pipeMojiScript编写map。只不过这个程序运行正常,会打印出数字 1 到 5,每次输出前会暂停 1 秒。

const sleepThenLog = pipe ([
  sleep (1000),
  log
])

const main = pipe ([
  range (1) (6),
  map (sleepThenLog)
])
Enter fullscreen mode Exit fullscreen mode

想玩更多?MojiScript 入门指南:FizzBu​​zz

谷歌搜索相关内容

概括

将 sleep 转换为 promise 风格的函数,可以为异步代码的运行方式提供更多选项。

RamdapipeP或 MojiScript 的某些功能可能比其他功能pipe更简洁Promisesasync/await

异步映射功能强大。

需要注意的是(如下所述),此实现不支持取消操作。因此,如果您需要取消clearTimeout操作,则需要修改此函数。

我的文章大量运用了函数式 JavaScript,如果您需要更多相关内容,请在这里或 Twitter 上关注我@joelnet

阅读我的其他文章:

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

我是如何将90%的JavaScript代码扔进垃圾桶后,重新发现我对它的热爱的

干杯!

文章来源:https://dev.to/joelnet/settimeout-is-a-callback-style-function-what-would-happen-if-we-change-that-3fei