子非鱼安知鱼之乐
仔细想想也写了1年多javascript了,从c#入坑JS后,里里外外也踩了不少坑。本质上还是两种语言的思维不一样。像之前c#,一个线程就是执行一件事情,在我看来大部分逻辑都是可以用顺序,判断和循环来解决的。所以一开始写JS,对异步,回调,甚至promise都很懵圈。不过既然存在就有道理,毕竟当前执行线程一旦阻塞后那WEB页面就卡死,一切就凉凉了。于是我也简单整理了一下我接触过的利用多线程和异步分别去实现非阻塞I/O密集型任务都方式。
多线程模型开发
之前做WPF时,.net提供了一个BackgroundWorker的类库。不得不说微软开发体验好,该提供都都提供了。。
MSDN对应BackgroundWorker文档
private void InitializeBackgroundWorker()
{
backgroundWorker1.DoWork += new DoWorkEventHandler (backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
复制代码
DoWork 事件是执行都任务
RunWorkerCompleted 任务执行完后触发都事件
ProgressChanged 执行中进度获取
实际我们开发都时候,流程是这样,原谅我灵魂画手
uiandthread
用子线程去执行网络请求,读写文件等操作,待子线程执行完毕,将结果直接返回给UI线程。如果UI线程需要进度条,也可以
异步开发
javascript里就简单多了。打个比方
request('api', (res) => {
成功回调
}, () => {
失败回调
});
复制代码
但是回调用多了也恶心,有时候数据流逻辑上是同步使用,比如我必须得到这个数据结果才能下一步网络请求,下一步网络请求的结果拿来去做下下步请求。
request('api', (res) => {
成功回调
request('api2', res, (res2) => {
成功回调
request('api3', res2, (res) => {
成功回调
}, () => {
失败回调
});
}, () => {
失败回调
});
}, () => {
失败回调
});
复制代码
所以Promise出现了。Promise,我个人简单总结就是把嵌套逻辑摆平为同步代码逻辑。
request('api')
.then(res => {
return request('api2', res)
})
.then(res2 => {
return request('api3', res2)
})
.then(res3 => {
成功回调
})
.catch(e => {
失败回调
});
复制代码
这样代码看起来就没那么乱了。不过,这样就最好了嘛?自然不是,既然都用到promise了,直接撸上await/async更是美滋滋。
async test() {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
}
复制代码
注意:函数体内如果使用await关键字则该函数体必须用async标志。这样写起来,跟写java,c#等代码如出一辙了。(后来听说C#也加上了async/await异步方法)。对了,改为同步写法后,我们对异常处理就更简单了。
async test() {
try {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
} catch(e) {
错误处理
}
}
复制代码
注意: try/catch只能捕捉同步代码的异常,如果写成下面那样,
test() {
try {
let res = request('api');
let res2 = request('api2', res);
let res3 = request('api3', res2);
} catch(e) {
错误处理
}
}
test2() {
try {
request('api')
.then(res => {
return request('api2', res)
})
.then(res2 => {
return request('api3', res2)
})
.then(res3 => {
成功回调
})
.catch(e => {
失败回调
});
} catch(e) {
错误处理
}
}
复制代码
可是无法正确catch到异常哟。有时候写nodejs时,出错了需要重试,这样用try/catch抓到异常后便可直接进行重试操作,远比之前回调方式要简单的多。而且异常也可一直抛出到外部,由外部调用代码去处理。
async main() {
try {
await test();
} catch(e) {
错误处理
await test();
}
}
async test() {
let res = await request('api');
let res2 = await request('api2', res);
let res3 = await request('api3', res2);
return res3
}
复制代码
总结
线程模型同步逻辑,异常捕捉与处理等都比异步回调方式要友好。但是线程创建,销毁和切换是有开销都。
JS的异步回调,免去线程都开销。但是回调代码读起来也挺累的哟。如果大家用ES6和typescipt的话,用async/await的话就会友好很多。