业务当中写Android异步任务一直是一项挑战,以往的回调和线程管理方式比较复杂和繁琐,造成代码难以维护和阅读。在前端领域中JavaScript其实也面临同样的问题,Promise 就是它的比较主流的一种解法。在尝试使用Promise之前我们也针对Android现有的一些异步做了详细的对比。
对于Android开发的同学,可能很多人不太熟悉Promise,它主要是前端的实践,所以先解析概念。
Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
}).then(function(value) {
console.log('resolved.');
}).catch(function(error) {
console.log('发生错误!', error);
});
实例化一个Promise对象,构造函数接受一个函数作为参数,该参数分别是
resolve
和reject
。 resolve函数:将Promise 对象状态从pending 变成 resolved reject函数:将Promise 对象状态从 pending 变成 rejected then函数:回调 resolved状态的结果 catch函数:回调 rejected状态的结果
可以看到Promise的状态是非常简单且清晰的,这也让它在实现异步编程减少很多认知负担。
前面说的Promise 不就是 JavaScript 异步编程的一种思想吗,那这跟 Android 开发有什么关系?虽然前端和终端领域有所不同,但面临的问题其实是大同小异的,比如常见的异步回调导致回调地狱,逻辑处理不连贯等问题。
从事Android开发的同学应该对以下异步编程场景比较熟悉:
这里可以停顿思考一下,如果利用 Android常规的方式去实现以上场景,你会怎么做?你的脑子可能有以下解决方案:
以上方案都能在Android中实现异步任务处理,但或多或少存在一些问题和适用场景,我们详细剖析下各自的优缺点:
通过不同的异步实现方式的对比,可以发现每种实现方式都有适用场景,我们面对业务复杂度也是不一样的,每一种解决方案都是为了降低业务复杂度,用更低成本的方式来编码,但我们也知道代码写出来是给人看的,是需要持续迭代和维护,类似RxJava 这种框架于我们而言太复杂了,繁琐的操作符容易写出不易维护的代码,简单易理解应该是更好的追求,而不是炫技,所以我们才会探索用更轻量更简洁的编码方式来提升团队的代码一致性,就目前而言使用 Promise 来写代码将会有以下好处:
.then().then()...
,从而使代码编写和阅读更直观可以看到以上代码存在以下问题:
使用 Promise重构后:
可以看到有以下变化:
代码存在以下问题:
使用Promise重构后:
第一个Promise处理长链接Push监听 ,设置5s超时,超时异常发生回调except方法,判断throwable 类型,如果为PromiseTimeoutException实例对象,则执行降级短链接。短链接是另外一个Promise,通过这种方式将逻辑都完全结果,代码不会割裂,逻辑更连贯。
短链接轮训查单逻辑使用Promise实现:
这段代码把复杂的延时、条件判断、重试策略都通过Promise这个框架实现了,少了很多临时变量,代码量更少,逻辑更清晰。
后面针对降级策略重构成竞速模型,采用Promise.any很轻松得实现代码重构,代码如下图所示。
本文提供一种异步编程的思路,借鉴了Promise思想来重构了Android的异步代码。通过Promise组件提供的多种并发模型能够更优雅的解决绝大部分的场景需求。
如果跟Activity或Fragment生命周期绑定,需要在生命周期结束时,取消掉promise的线程运行,否则可能会有内存泄露;这里可以采用AbortController来实现更优雅的中断 Promise。
● 多任务并行请求 Promise.all():接受任意个Promise对象,并发执行异步任务。全部任务成功,有一个失败则视为整体失败。 Promise.allSettled(): 任务优先,所有任务必须执行完毕,永远不会进入失败状态。 Promise.any():接受任意个Promise对象,并发执行异步任务。等待其中一个成功即为成功,全部任务失败则进入错误状态,输出错误列表。 ● 多任务竞速场景 Promise.race(): 接受任意个Promise对象,并发执行异步任务。时间是第一优先级,多个任务以最先返回的那个结果为准,此结果成功即为整体成功,失败则为整体失败。
1. 避免过长的链式调用:虽然Promise可以通过链式调用来避免回调地狱,但是如果Promise的链过长,代码的可读性和维护性也会变差。
2. 及时针对Promise进行abort操作:
Promise使用不当可能会造成内存泄露,比如未调用abort,页面取消未及时销毁proimse。
3. 需要处理except异常回调,处理PromiseException.
4. 可以使用validation来实现规则校验,减少if-else的规则判断
2.
1. 状态机实现(pending、fulfilled、rejected)
2. 默认使用 ForkJoinPool 线程池,适合计算密集型任务。针对阻塞IO类型,可以使用内置ThreadPerTaskExecutor 简单线程池模型。
3.
● Promise - JavaScript | MDN
● Promises/A+
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!