Jef*_*eff 1 javascript asynchronous functional-programming node.js ramda.js
我正在将一些旧节点模块重构为更实用的样式.对于FP来说,我就像第二年的新生一样.我一直在忙着处理大型异步流程.这是一个示例,我正在向db发出请求,然后缓存响应:
// Some external xhr/promise lib
const fetchFromDb = make => {
return new Promise(resolve => {
console.log('Simulate async db request...'); // just simulating a async request/response here.
setTimeout(() => {
console.log('Simulate db response...');
resolve({ make: 'toyota', data: 'stuff' });
}, 100);
});
};
// memoized fn
// this caches the response to getCarData(x) so that whenever it is invoked with 'x' again, the same response gets returned.
const getCarData = R.memoizeWith(R.identity, (carMake, response) => response.data);
// Is this function pure? Or is it setting something outside the scope (i.e., getCarData)?
const getCarDataFromDb = (carMake) => {
return fetchFromDb(carMake).then(getCarData.bind(null, carMake));
// Note: This return statement is essentially the same as:
// return fetchFromDb(carMake).then(result => getCarData(carMake, result));
};
// Initialize the request for 'toyota' data
const toyota = getCarDataFromDb('toyota'); // must be called no matter what
// Approach #1 - Just rely on thenable
console.log(`Value of toyota is: ${toyota.toString()}`);
toyota.then(d => console.log(`Value in thenable: ${d}`)); // -> Value in thenable: stuff
// Approach #2 - Just make sure you do not call this fn before db response.
setTimeout(() => {
const car = getCarData('toyota'); // so nice!
console.log(`later, car is: ${car}`); // -> 'later, car is: stuff'
}, 200);Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>Run Code Online (Sandbox Code Playgroud)
我非常喜欢用于缓存大型JSON对象和其他计算属性的memoization.但是由于很多异步请求的响应依赖于彼此的工作,我无法跟踪我拥有的信息和时间.我想远离使用promises来管理流量.它是一个节点应用程序,因此使事物同步以确保可用性阻止事件循环并真正影响性能.
我更喜欢方法#2,在那里我可以简单地获取汽车数据getCarData('toyota').但缺点是我必须确保已经返回了响应.随着做法#1我会一直使用它减少问题与方法#2,但引入了它自己的问题thenable.
问题:
getCarFromDb因为它上面写的是一个纯函数?如果不是,那怎么不是副作用?这里几乎是一个哲学问题,这里是否存在副作用.调用它会更新memoization缓存.但这本身没有可观察到的副作用.所以我会说这实际上是纯粹的.
更新:评论指出,因为这称为IO,它永远不会是纯粹的.那是正确的.但这就是这种行为的本质.它作为一个纯函数没有意义.我上面的答案只是副作用,而不是纯度.
我不能代表整个FP社区,但我可以告诉你,Ramda团队(免责声明:我是Ramda作者)更喜欢避免Promises,更喜欢更合法的类型,如Futures或Tasks.但是你在这里遇到的问题与那些取代Promises的类型有关.(关于以下这些问题的更多信息.)
这里有一个中心点:如果你正在进行异步编程,它将扩展到触及它的应用程序的每一位.没有什么可以改变这个基本事实.使用Promises/Tasks/Futures有助于避免使用基于回调的代码的一些模板,但它要求您将后响应/拒绝代码放在then/ mapfunction中.使用async/await可以帮助您避免使用基于Promise的代码的一些模板,但它要求您将后响应/拒绝代码放在async函数中.如果有一天我们async/await将其他东西叠加在一起,它可能具有相同的特征.
(虽然我建议你看Futures或Tasks而不是Promises,下面我只会讨论Promises.无论如何都应该适用相同的想法.)
如果你要记忆任何东西,请记住结果Promises.
但是,如果处理异步,则必须将依赖于异步调用结果的代码放入函数中.我假设setTimeout您的第二种方法仅用于演示目的:使用超时等待网络上的DB结果非常容易出错.但即便如此setTimeout,其余代码也在setTimeout回调中运行.
因此,不要试图将数据分离的情况分开,而不是在数据已经缓存的时候,而不是在任何地方使用相同的技术:myPromise.then(... my code ... ).这可能看起来像这样:
// getCarData :: String -> Promise AutoInfo
const getCarData = R.memoizeWith(R.identity, make => new Promise(resolve => {
console.log('Simulate async db request...')
setTimeout(() => {
console.log('Simulate db response...')
resolve({ make: 'toyota', data: 'stuff' });
}, 100)
})
)
getCarData('toyota').then(carData => {
console.log('now we can go', carData)
// any code which depends on carData
})
// later
getCarData('toyota').then(carData => {
console.log('now it is cached', carData)
})Run Code Online (Sandbox Code Playgroud)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>Run Code Online (Sandbox Code Playgroud)
在这种方法中,无论何时需要汽车数据,都可以打电话getCarData(make).它只是第一次实际调用服务器.之后,它将Promise从缓存中提供.但是你到处都用相同的结构来处理它.
我只看到一个合理的选择.我无法判断您是否在进行剩余呼叫之前必须等待数据的讨论意味着您可以预先获取数据.如果是这种情况,那么还有一种可能性,即允许您跳过备忘录的可能性:
// getCarData :: String -> Promise AutoInfo
const getCarData = make => new Promise(resolve => {
console.log('Simulate async db request...')
setTimeout(() => {
console.log('Simulate db response...')
resolve({ make: 'toyota', data: 'stuff' });
}, 100)
})
const makes = ['toyota', 'ford', 'audi']
Promise.all(makes.map(getCarData)).then(allAutoInfo => {
const autos = R.zipObj(makes, allAutoInfo)
console.log('cooking with gas', autos)
// remainder of app that depends on auto data here
})Run Code Online (Sandbox Code Playgroud)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>Run Code Online (Sandbox Code Playgroud)
但是这意味着在获取所有数据之前没有任何东西可用.根据各种因素的不同,这可能也可能不适合你.在许多情况下,它甚至都不可能或不可取.但你的有可能是有帮助的.
关于您的代码的一个技术点:
Run Code Online (Sandbox Code Playgroud)const getCarDataFromDb = (carMake) => { return fetchFromDb(carMake).then(getCarData.bind(null, carMake)); };
有没有理由用getCarData.bind(null, carMake)而不是() => getCarData(carMake)?这似乎更具可读性.
| 归档时间: |
|
| 查看次数: |
1239 次 |
| 最近记录: |