假设我们有一系列需要调用的网络请求,有的并行,有点串行,有的需要处理异常,那么如何打造这样一条执行链呢?
先模拟一些网络请求。
let R= require('ramda');
const getUser = (userId) => new Promise((resolve,reject) => {
setTimeout(() => {
resolve({users: {
JOE: {
name: 'Joe',
followers: ['STEVE', 'SUZY']
}
}})
}, 500);
});
const getNameList = () => new Promise((resolve,reject)=>{
setTimeout(() => {
resolve([
{name:'name1'},
{name:'name2'},
{name:'name3'}
])
}, 400);
});
const getProductByUserName = (name) => new Promise((resolve,reject)=>{
setTimeout(() => {
// reject(new Error('nothing'))
resolve('success');
}, 1000);
})
我们假设第一个串行,后两个并行(并且捕获错误)
1.命令式写法:
const getResult = async (userId)=> {
let {users:{JOE:{name}}} = await getUser('userId');
//捕获错误
let tasks = [getProductByUserName(name),getNameList()].map(v=>v.catch(err=>err));
//并发执行
return Promise.all(tasks);
//不用PromiseAll的写法:
//return [await p1,await p2,await p3];
}
const result = getResult().then((v)=>console.log(v));
2.函数式写法:
1.使用composeP,composeP是针对promise对象的组合子(自动将下一个调用放在then里面)
//并发执行
const parallel = R.compose(
(v)=>Promise.all(v),//这样转一下是因为直接传入Promise.all,node下会报错,原因不明
(v)=>[getNameList(),getProductByUserName(v).catch(err=>err)],
R.path(['users','JOE','name'])
)
const getResult = R.composeP(
parallel,
getUser
);
const result = getResult().then((v)=>console.log(v));
但是这样写也并不怎么清晰,我们尝试创建一个通用的并发执行的方法。
//并行执行promise,该方法接受两个参数,第一个是promise方法数组,第二个是以上方法需要的参数,方法和参数是一一对应的,不需要参数的方法,需放在数组末尾。
//并行promise
const parallel = (...Ps) => R.compose(
(v)=>Promise.all(v),
R.map(R.otherwise(R.identity)),
(...params)=>R.reduce((arr,fn)=>[...arr,fn(params.shift())],[],Ps)
)
重新组织我们的调用:
const getResult = R.composeP(
parallel(getProductByUserName,getNameList),
R.path(['users','JOE','name']),
getUser
);
const time = new Date();
getResult('userId').then((result)=>{
console.log("time:",new Date() - time);
console.log("TCL: result", result)
}).catch(err=>console.log(err))
这样的实现可以用清晰的代码处理复杂的异步调用(即使再复杂,也只是增加了composeP组合的长度而已)
(以上代码都经过测试,直接可用。)之后想讲一下函数提升。