setTimeout 是一个回调函数。如果我们修改它会发生什么?
Node 风格的回调函数
承诺设置超时
利用睡眠
异步函数组合
异步映射
在 for 循环中使用 setTimeout
谷歌搜索相关内容
概括
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
如今,将 Node 风格的回调函数转换为 Promise 风格的函数已成为一种常见的做法。那么,为什么我们没有对 也这样做呢setTimeout?
比起 Node 风格的回调函数,使用 Promise 风格的函数的主要原因是避免回调地狱。
谁都不想看到那种场面。
查看setTimeout(及其兄弟函数setInterval或setImmediate),我可以清楚地看出这是一个回调式函数。
setTimeout(callback, 1000);
// --------
// \
// See that? Right there. A callback!
setTimeout然而,将回调函数转换为 Promise 的做法却极其罕见。这种做法是如何setTimeout悄无声息地被人们接受的?它与其他方法有何setTimeout不同,以至于能够被认可?
我说不。
Node 风格的回调函数
setTimeout可能被忽略的原因是,尽管它显然是一个回调风格的函数,但它不是节点风格的回调函数,两者略有不同。
首先,让我们看一下 node 风格的回调函数,以便更好地了解它们之间的区别。fs.readFile这是一个 node 风格回调函数的绝佳示例。
fs.readFile(path[, options], callback)
// --------
// /
// callback must be last
回调函数本身必须如下所示:
const callback = (err, data) => { /* ... */ }
// --- ----
// / \
// error first data last
如果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)
很遗憾,util.promisify这样做行不通。首先,因为回调函数不是最后一个参数。其次,因为回调函数不符合接口(err, data)规范。
承诺设置超时
幸运的是,手动转换同样简单。我将把这个新函数命名为sleep……
const sleep = milliseconds => value => new Promise (resolve =>
setTimeout(() => resolve(value), milliseconds)
)
关于这段代码,我想指出几点关键事项。
sleep是咖喱味的。你稍后就会明白为什么了。sleep接受一个参数value,然后解析该参数value。稍后您就会明白原因。
利用睡眠
现在,在代码中添加暂停就像使用 Promise 一样简单。
const log => msg => console.log(msg)
sleep(1000)('Hello World').then(log)
这当然很好,但这并不是我写这篇文章的原因。
真正让我兴奋的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)
因为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()
说实话,我喜欢这些问题的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()
哇,这代码真漂亮!
但既然我们已经在讨论函数组合了,那么很容易将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()
异步映射
MojiScript 还具有独特的异步映射功能。(敬请期待近期发布的专题文章。)
异步映射是我决定使用MojiScript而pipe不是Ramda来编写这些示例的原因。pipeP到目前为止,这些示例使用 Ramda 也能正常运行pipeP。从现在开始,这些示例将完全基于 MojiScript。
map让我们来看一些代码!异步执行Ajax 调用是不是很容易?
const main = pipe ([
({ start, end }) => range (start) (end + 1),
map (fetchLogWait),
])
main ({ start: 1, end: 3 })
简直太简单了!
所有内容都整合到一个可运行的代码块中:
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 })
这段代码已经非常符合 DRY 原则了!
在 for 循环中使用 setTimeout
如果你还没遇到过这个问题,它经常出现在 JavaScript 面试中。这段代码运行结果与预期不符。请问输出结果是什么?
for (var i = 1; i < 6; i++) {
setTimeout(() => console.log(i), 1000)
}
如果你没猜到它会暂停 1 秒钟,然后6一次性打印五个 ',那你就错了。
同样的程序,使用pipeMojiScript编写map。只不过这个程序运行正常,会打印出数字 1 到 5,每次输出前会暂停 1 秒。
const sleepThenLog = pipe ([
sleep (1000),
log
])
const main = pipe ([
range (1) (6),
map (sleepThenLog)
])
想玩更多?MojiScript 入门指南:FizzBuzz
谷歌搜索相关内容
概括
将 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


