JSBox 异步编程详解:让你的脚本如丝般顺滑
在 JSBox 开发中,异步编程是一个绕不开的核心概念。掌握异步编程,不仅能让你的脚本运行更加流畅,还能充分发挥 JSBox 的强大功能。让我们深入探讨 JSBox 中异步编程的方方面面。
一、为什么 JSBox 需要异步编程
1. 移动设备的特殊性
移动设备与桌面设备最大的区别在于资源的限制。iPhone 即使性能再强,也需要同时运行多个应用,保持系统流畅。如果你的 JSBox 脚本采用同步方式执行耗时操作,会导致整个界面"卡死",用户体验极差。
举个简单的例子:
// 错误示范:同步方式
function downloadData() {
// 假设这个操作需要3秒
let data = $http.getSync("https://api.example.com/data");
// 这3秒内,整个界面都会卡住
$ui.alert(data);
}
2. JavaScript 的单线程特性
JavaScript 是单线程语言,这意味着同一时间只能执行一个任务。如果某个任务耗时较长,后续所有任务都必须等待。异步编程通过事件循环机制,让耗时操作在后台执行,不阻塞主线程。
3. 用户体验的要求
用户期望的是即时响应。点击按钮应该立即有反馈,滑动列表应该流畅无卡顿。异步编程确保了 UI 操作的优先级,让用户感受不到后台正在进行的复杂计算或网络请求。
二、JSBox 中的异步编程方式
1. 回调函数(Callback)
回调函数是最基础的异步编程方式。JSBox 的许多原生 API 都支持回调函数。
// 网络请求示例
$http.get({
url: "https://api.github.com/users/github",
handler: function(resp) {
// 这是回调函数,请求完成后执行
let data = resp.data;
$ui.alert({
title: "用户信息",
message: `用户名: ${data.login}\n仓库数: ${data.public_repos}`
});
}
});
// 文件读取示例
$file.read({
path: "data.json",
handler: function(data) {
if (data) {
let json = JSON.parse(data.string);
console.log("读取成功:", json);
} else {
console.log("文件不存在");
}
}
});
2. Promise:优雅的链式调用
Promise 解决了回调地狱的问题,让异步代码更加清晰。
// 将回调转换为 Promise
function httpGet(url) {
return new Promise((resolve, reject) => {
$http.get({
url: url,
handler: function(resp) {
if (resp.error) {
reject(resp.error);
} else {
resolve(resp.data);
}
}
});
});
}
// 使用 Promise 链
httpGet("https://api.github.com/users/github")
.then(userData => {
console.log("获取用户信息成功");
// 返回新的 Promise,获取用户的仓库
return httpGet(userData.repos_url);
})
.then(repoData => {
console.log(`用户有 ${repoData.length} 个仓库`);
// 获取第一个仓库的详情
if (repoData.length > 0) {
return httpGet(repoData[0].url);
}
})
.then(repoDetail => {
if (repoDetail) {
$ui.alert(`第一个仓库: ${repoDetail.name}`);
}
})
.catch(error => {
console.error("请求失败:", error);
});
3. async/await:同步般的异步体验
async/await 让异步代码看起来像同步代码,极大提升了代码可读性。
// 定义异步函数
async function getUserInfo(username) {
try {
// await 会等待 Promise 完成
let userData = await httpGet(`https://api.github.com/users/${username}`);
let repoData = await httpGet(userData.repos_url);
return {
name: userData.name,
company: userData.company,
repoCount: repoData.length,
totalStars: repoData.reduce((sum, repo) => sum + repo.stargazers_count, 0)
};
} catch (error) {
console.error("获取用户信息失败:", error);
return null;
}
}
// 使用异步函数
async function main() {
// 显示加载提示
$ui.loading(true);
let info = await getUserInfo("torvalds");
$ui.loading(false);
if (info) {
$ui.alert({
title: info.name,
message: `公司: ${info.company}\n仓库数: ${info.repoCount}\n总星数: ${info.totalStars}`
});
}
}
main();
4. 定时器和延迟执行
JSBox 提供了多种定时执行的方式。
// setTimeout - 延迟执行
$delay(2, () => {
console.log("2秒后执行");
});
// setInterval - 重复执行
let counter = 0;
let timer = $timer.schedule({
interval: 1,
handler: function() {
counter++;
console.log(`计数: ${counter}`);
if (counter >= 10) {
timer.invalidate(); // 停止定时器
}
}
});
// 使用 Promise 实现延迟
function delay(seconds) {
return new Promise(resolve => {
$delay(seconds, resolve);
});
}
// 在 async 函数中使用
async function animatedAlert() {
$ui.alert("3");
await delay(1);
$ui.alert("2");
await delay(1);
$ui.alert("1");
await delay(1);
$ui.alert("开始!");
}
三、实际应用场景详解
场景1:批量图片下载器
这是一个典型的需要异步处理的场景:下载多张图片,显示进度,且不阻塞界面。
class ImageDownloader {
constructor(urls) {
this.urls = urls;
this.results = [];
this.progress = 0;
}
// 下载单张图片
async downloadImage(url, index) {
return new Promise((resolve, reject) => {
$http.download({
url: url,
progress: (bytesWritten, totalBytes) => {
// 更新单个图片的下载进度
let percentage = bytesWritten / totalBytes;
this.updateProgress(index, percentage);
},
handler: (resp) => {
if (resp.error) {
reject(resp.error);
} else {
resolve(resp.data);
}
}
});
});
}
// 更新进度显示
updateProgress(index, percentage) {
// 更新 UI 上的进度条
$("progress").value = (index + percentage) / this.urls.length;
$("progressLabel").text = `下载中: ${index + 1}/${this.urls.length}`;
}
// 并发下载所有图片
async downloadAll() {
// 创建所有下载任务
let tasks = this.urls.map((url, index) =>
this.downloadImage(url, index)
.then(data => {
this.results[index] = data;
return data;
})
.catch(error => {
console.error(`图片 ${index} 下载失败:`, error);
return null;
})
);
// 等待所有下载完成
await Promise.all(tasks);
return this.results.filter(data => data !== null);
}
// 限制并发数的下载
async downloadAllWithLimit(limit = 3) {
let results = [];
let executing = [];
for (let i = 0; i < this.urls.length; i++) {
const task = this.downloadImage(this.urls[i], i)
.then(data => {
results[i] = data;
return i;
});
executing.push(task);
if (executing.length >= limit) {
// 等待最快完成的一个
await Promise.race(executing);
executing = executing.filter(t => t !== task);
}
}
// 等待剩余的任务
await Promise.all(executing);
return results;
}
}
// 使用示例
async function batchDownload() {
let urls = [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
// ... 更多URL
];
// 创建界面
$ui.render({
props: {
title: "批量下载"
},
views: [{
type: "progress",
props: {
id: "progress",
value: 0
},
layout: (make, view) => {
make.left.right.inset(20);
make.centerY.equalTo(view.super);
make.height.equalTo(4);
}
}, {
type: "label",
props: {
id: "progressLabel",
text: "准备下载...",
align: $align.center
},
layout: (make, view) => {
make.centerX.equalTo(view.super);
make.bottom.equalTo($("progress").top).offset(-10);
}
}]
});
let downloader = new ImageDownloader(urls);
let images = await downloader.downloadAllWithLimit(3);
$ui.alert(`下载完成!成功: ${images.length}/${urls.length}`);
}
场景2:实时数据监控
监控多个数据源,实时更新显示,这需要精心设计的异步架构。
class DataMonitor {
constructor(sources) {
this.sources = sources; // 数据源配置
this.data = {}; // 存储最新数据
this.intervals = []; // 存储定时器
this.listeners = []; // 数据更新监听器
}
// 添加数据更新监听器
addListener(callback) {
this.listeners.push(callback);
}
// 通知所有监听器
notifyListeners(source, data) {
this.listeners.forEach(callback => {
callback(source, data);
});
}
// 获取单个数据源
async fetchData(source) {
try {
let response = await httpGet(source.url);
// 数据处理
let processedData = source.processor ?
source.processor(response) : response;
// 更新缓存
this.data[source.name] = {
value: processedData,
timestamp: new Date(),
status: 'success'
};
// 通知监听器
this.notifyListeners(source.name, processedData);
} catch (error) {
this.data[source.name] = {
error: error.message,
timestamp: new Date(),
status: 'error'
};
this.notifyListeners(source.name, null);
}
}
// 启动监控
start() {
this.sources.forEach(source => {
// 立即获取一次
this.fetchData(source);
// 设置定时更新
let timer = $timer.schedule({
interval: source.interval || 5,
handler: () => {
this.fetchData(source);
}
});
this.intervals.push(timer);
});
}
// 停止监控
stop() {
this.intervals.forEach(timer => timer.invalidate());
this.intervals = [];
}
// 获取所有数据的快照
getSnapshot() {
return JSON.parse(JSON.stringify(this.data));
}
}
// 使用示例
function startMonitoring() {
// 配置数据源
let sources = [
{
name: "股票价格",
url: "https://api.example.com/stock/AAPL",
interval: 5,
processor: (data) => ({
price: data.price,
change: data.change,
changePercent: data.changePercent
})
},
{
name: "天气信息",
url: "https://api.example.com/weather",
interval: 300, // 5分钟更新一次
processor: (data) => ({
temperature: data.main.temp,
description: data.weather[0].description
})
},
{
name: "系统状态",
url: "https://api.example.com/system/status",
interval: 10,
processor: (data) => ({
cpu: data.cpu_usage,
memory: data.memory_usage,
disk: data.disk_usage
})
}
];
let monitor = new DataMonitor(sources);
// 添加数据更新处理
monitor.addListener((source, data) => {
if (data) {
updateUI(source, data);
// 检查告警条件
checkAlerts(source, data);
} else {
showError(source);
}
});
// 启动监控
monitor.start();
// 返回控制器供后续使用
return monitor;
}
function updateUI(source, data) {
// 更新界面显示
$(`${source}_value`).text = JSON.stringify(data, null, 2);
$(`${source}_status`).bgcolor = $color("green");
}
function checkAlerts(source, data) {
// 异步检查告警条件
$thread.background({
delay: 0,
handler: function() {
if (source === "股票价格" && data.changePercent < -5) {
$thread.main({
handler: function() {
$push.schedule({
title: "股票预警",
body: `股票跌幅超过5%: ${data.changePercent}%`,
delay: 0
});
}
});
}
}
});
}
场景3:串行任务流程控制
有时候我们需要按照特定顺序执行一系列异步任务,每个任务依赖前一个任务的结果。
class TaskFlow {
constructor() {
this.tasks = [];
this.context = {}; // 任务间共享的上下文
}
// 添加任务
addTask(name, handler) {
this.tasks.push({
name: name,
handler: handler
});
return this; // 支持链式调用
}
// 执行所有任务
async execute() {
console.log("开始执行任务流程");
for (let i = 0; i < this.tasks.length; i++) {
let task = this.tasks[i];
console.log(`执行任务 ${i + 1}/${this.tasks.length}: ${task.name}`);
try {
// 显示进度
$ui.loading({
text: task.name
});
// 执行任务,传入上下文
let result = await task.handler(this.context);
// 保存结果到上下文
this.context[task.name] = result;
console.log(`任务 ${task.name} 完成`);
} catch (error) {
console.error(`任务 ${task.name} 失败:`, error);
$ui.loading(false);
// 询问是否继续
let continueFlow = await this.askContinue(task.name, error);
if (!continueFlow) {
throw new Error(`任务流程在 ${task.name} 中断`);
}
}
}
$ui.loading(false);
return this.context;
}
// 询问是否继续
askContinue(taskName, error) {
return new Promise((resolve) => {
$ui.alert({
title: "任务失败",
message: `任务 "${taskName}" 执行失败:\n${error.message}\n\n是否继续执行后续任务?`,
actions: [
{
title: "继续",
handler: function() {
resolve(true);
}
},
{
title: "中止",
handler: function() {
resolve(false);
}
}
]
});
});
}
}
// 实际应用:发布文章到多个平台
async function publishArticle() {
let flow = new TaskFlow();
flow
.addTask("读取文章", async (context) => {
// 从文件或剪贴板读取文章内容
let article = $clipboard.text;
if (!article) {
throw new Error("剪贴板中没有内容");
}
return {
title: article.split('\n')[0],
content: article
};
})
.addTask("处理图片", async (context) => {
// 查找文章中的图片,上传到图床
let imageUrls = [];
let matches = context.读取文章.content.match(/!\[.*?\]\((.*?)\)/g) || [];
for (let match of matches) {
let localPath = match.match(/\((.*?)\)/)[1];
if (localPath.startsWith("file://")) {
// 上传本地图片
let uploadedUrl = await uploadImage(localPath);
imageUrls.push({
local: localPath,
remote: uploadedUrl
});
}
}
return imageUrls;
})
.addTask("发布到平台A", async (context) => {
// 替换图片链接
let content = context.读取文章.content;
context.处理图片.forEach(img => {
content = content.replace(img.local, img.remote);
});
// 发布到平台A
let response = await httpPost("https://platform-a.com/api/publish", {
title: context.读取文章.title,
content: content
});
return response.articleId;
})
.addTask("发布到平台B", async (context) => {
// 平台B可能需要不同的格式
let formattedContent = convertToMarkdown(context.读取文章.content);
let response = await httpPost("https://platform-b.com/api/publish", {
title: context.读取文章.title,
body: formattedContent,
tags: ["JSBox", "技术"]
});
return response.url;
})
.addTask("生成报告", async (context) => {
// 生成发布报告
let report = `发布成功!\n\n`;
report += `平台A ID: ${context.发布到平台A}\n`;
report += `平台B URL: ${context.发布到平台B}\n`;
report += `上传图片数: ${context.处理图片.length}`;
// 保存报告
$file.write({
path: `publish_report_${Date.now()}.txt`,
data: $data({string: report})
});
return report;
});
try {
let result = await flow.execute();
$ui.alert({
title: "发布完成",
message: result.生成报告
});
} catch (error) {
$ui.alert({
title: "发布失败",
message: error.message
});
}
}
四、异步编程的常见陷阱与解决方案
1. 回调地狱
当多个异步操作需要按顺序执行时,使用回调函数会导致代码嵌套过深。
// 错误示范:回调地狱
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
getMoreData(d, function(e) {
// 嵌套太深,难以维护
});
});
});
});
});
// 解决方案:使用 async/await
async function getAllData() {
let a = await getData();
let b = await getMoreData(a);
let c = await getMoreData(b);
let d = await getMoreData(c);
let e = await getMoreData(d);
return e;
}
2. 错误处理不当
异步操作的错误处理容易被忽略,导致程序出现未捕获的异常。
// 错误示范:没有错误处理
async function riskyOperation() {
let data = await fetchData(); // 如果失败会抛出异常
return processData(data);
}
// 正确做法:完善的错误处理
async function safeOperation() {
try {
let data = await fetchData();
return processData(data);
} catch (error) {
console.error("操作失败:", error);
// 可以选择:
// 1. 返回默认值
return getDefaultData();
// 2. 重新抛出特定错误
throw new Error(`数据处理失败: ${error.message}`);
// 3. 记录错误并通知用户
logError(error);
$ui.alert("操作失败,请稍后重试");
}
}
3. 并发控制不当
同时发起太多异步操作可能导致资源耗尽或请求被限制。
// 错误示范:无限制并发
async function downloadAllImages(urls) {
// 同时下载所有图片,可能导致内存溢出或请求被拒绝
let promises = urls.map(url => downloadImage(url));
return Promise.all(promises);
}
// 解决方案:并发数量控制
async function downloadWithConcurrencyLimit(urls, limit = 5) {
let results = [];
let executing = [];
for (const [index, url] of urls.entries()) {
const promise = downloadImage(url).then(result => {
results[index] = result;
return index;
});
executing.push(promise);
if (executing.length >= limit) {
// 等待最快的一个完成
const completed = await Promise.race(executing);
executing = executing.filter(p => p !== promise);
}
}
// 等待所有剩余的
await Promise.all(executing);
return results;
}
// 使用第三方库的方案
class PromisePool {
constructor(concurrency) {
this.concurrency = concurrency;
this.current = 0;
this.queue = [];
}
async run(task) {
while (this.current >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.current++;
try {
return await task();
} finally {
this.current--;
if (this.queue.length > 0) {
const resolve = this.queue.shift();
resolve();
}
}
}
}
4. 内存泄漏
长时间运行的异步操作如果不正确清理,会导致内存泄漏。
class ResourceManager {
constructor() {
this.timers = new Set();
this.requests = new Map();
}
// 创建可取消的定时器
createTimer(callback, interval) {
let timer = $timer.schedule({
interval: interval,
handler: callback
});
this.timers.add(timer);
return {
cancel: () => {
timer.invalidate();
this.timers.delete(timer);
}
};
}
// 创建可取消的HTTP请求
async createRequest(url, options = {}) {
let cancelled = false;
let requestId = Date.now();
this.requests.set(requestId, {
cancelled: false
});
try {
let result = await httpGet(url);
if (this.requests.get(requestId).cancelled) {
throw new Error("Request cancelled");
}
return result;
} finally {
this.requests.delete(requestId);
}
}
// 清理所有资源
cleanup() {
// 取消所有定时器
this.timers.forEach(timer => timer.invalidate());
this.timers.clear();
// 标记所有请求为已取消
this.requests.forEach(request => request.cancelled = true);
this.requests.clear();
}
}
// 使用示例
let manager = new ResourceManager();
// 页面退出时清理
$app.listen({
exit: function() {
manager.cleanup();
}
});
五、高级异步模式
1. 事件驱动架构
创建一个完整的事件系统,实现组件间的解耦通信。
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅的函数
return () => {
this.events[event] = this.events[event].filter(cb => cb !== callback);
};
}
once(event, callback) {
const unsubscribe = this.on(event, (...args) => {
callback(...args);
unsubscribe();
});
}
emit(event, ...args) {
if (!this.events[event]) return;
// 异步执行所有回调
this.events[event].forEach(callback => {
Promise.resolve().then(() => callback(...args));
});
}
async emitAsync(event, ...args) {
if (!this.events[event]) return;
// 顺序执行所有异步回调
for (const callback of this.events[event]) {
await callback(...args);
}
}
}
// 应用示例:数据同步系统
class DataSyncSystem extends EventEmitter {
constructor() {
super();
this.setupEventHandlers();
}
setupEventHandlers() {
// 数据变更时自动同步
this.on('dataChanged', async (data) => {
await this.syncToCloud(data);
});
// 同步成功后更新UI
this.on('syncSuccess', (data) => {
this.updateUI(data);
});
// 错误处理
this.on('error', (error) => {
this.handleError(error);
});
}
async updateData(newData) {
try {
// 更新本地数据
await this.saveLocal(newData);
// 触发数据变更事件
this.emit('dataChanged', newData);
} catch (error) {
this.emit('error', error);
}
}
async syncToCloud(data) {
try {
let result = await uploadToCloud(data);
this.emit('syncSuccess', result);
} catch (error) {
this.emit('syncError', error);
}
}
}
2. 响应式编程模式
实现类似 RxJS 的观察者模式,处理异步数据流。
class Observable {
constructor(subscriber) {
this.subscriber = subscriber;
}
subscribe(observer) {
// 标准化观察者对象
const normalizedObserver = {
next: observer.next || (() => {}),
error: observer.error || (() => {}),
complete: observer.complete || (() => {})
};
// 执行订阅逻辑
const unsubscribe = this.subscriber(normalizedObserver);
return {
unsubscribe: unsubscribe || (() => {})
};
}
// 操作符:map
map(transform) {
return new Observable(observer => {
return this.subscribe({
next: value => observer.next(transform(value)),
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
// 操作符:filter
filter(predicate) {
return new Observable(observer => {
return this.subscribe({
next: value => {
if (predicate(value)) {
observer.next(value);
}
},
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
// 操作符:debounce
debounce(delay) {
return new Observable(observer => {
let timeoutId = null;
const subscription = this.subscribe({
next: value => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
observer.next(value);
}, delay);
},
error: err => observer.error(err),
complete: () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
observer.complete();
}
});
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
subscription.unsubscribe();
};
});
}
}
// 创建自定义Observable
function fromEvent(element, eventName) {
return new Observable(observer => {
const handler = event => observer.next(event);
element.addEventListener(eventName, handler);
return () => {
element.removeEventListener(eventName, handler);
};
});
}
// 使用示例:搜索框自动完成
function setupAutoComplete() {
const searchInput = $("searchInput");
const searchStream = fromEvent(searchInput, 'change')
.map(event => event.target.text)
.filter(text => text.length > 2)
.debounce(300);
const subscription = searchStream.subscribe({
next: async (searchTerm) => {
$ui.loading(true);
try {
const results = await searchAPI(searchTerm);
displayResults(results);
} catch (error) {
showError(error);
} finally {
$ui.loading(false);
}
},
error: error => console.error("搜索错误:", error)
});
// 清理
return () => subscription.unsubscribe();
}
六、最佳实践总结
1. 选择合适的异步模式
- 简单操作:使用回调函数
- 链式操作:使用 Promise
- 复杂流程:使用 async/await
- 事件处理:使用事件发射器
- 数据流:使用响应式编程
2. 错误处理原则
// 统一的错误处理器
class ErrorHandler {
static async handle(operation, options = {}) {
const {
retries = 3,
retryDelay = 1000,
fallback = null,
onError = null
} = options;
for (let i = 0; i < retries; i++) {
try {
return await operation();
} catch (error) {
console.error(`尝试 ${i + 1}/${retries} 失败:`, error);
if (onError) {
onError(error, i + 1);
}
if (i < retries - 1) {
await delay(retryDelay * (i + 1));
} else if (fallback !== null) {
return typeof fallback === 'function' ? fallback() : fallback;
} else {
throw error;
}
}
}
}
}
// 使用示例
const data = await ErrorHandler.handle(
() => fetchDataFromAPI(),
{
retries: 3,
retryDelay: 2000,
fallback: () => getDataFromCache(),
onError: (error, attempt) => {
console.log(`第 ${attempt} 次尝试失败`);
}
}
);
3. 性能优化技巧
// 缓存异步结果
class AsyncCache {
constructor(ttl = 60000) { // 默认缓存60秒
this.cache = new Map();
this.ttl = ttl;
}
async get(key, fetcher) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.value;
}
const value = await fetcher();
this.cache.set(key, {
value: value,
timestamp: Date.now()
});
return value;
}
clear() {
this.cache.clear();
}
}
// 批量请求优化
class BatchProcessor {
constructor(processor, batchSize = 10, delay = 100) {
this.processor = processor;
this.batchSize = batchSize;
this.delay = delay;
this.queue = [];
this.processing = false;
}
async add(item) {
return new Promise((resolve, reject) => {
this.queue.push({ item, resolve, reject });
if (!this.processing) {
this.startProcessing();
}
});
}
async startProcessing() {
this.processing = true;
while (this.queue.length > 0) {
const batch = this.queue.splice(0, this.batchSize);
try {
const results = await this.processor(batch.map(b => b.item));
batch.forEach((b, index) => {
b.resolve(results[index]);
});
} catch (error) {
batch.forEach(b => b.reject(error));
}
if (this.queue.length > 0) {
await delay(this.delay);
}
}
this.processing = false;
}
}
结语
异步编程是 JSBox 开发中的核心技能,掌握好异步编程不仅能让你的脚本运行更流畅,还能实现更复杂、更强大的功能。从简单的回调函数到复杂的响应式编程,每种模式都有其适用场景。
关键是要理解异步的本质——不阻塞主线程,让用户界面始终保持响应。在实际开发中,要根据具体需求选择合适的异步模式,做好错误处理,注意性能优化,避免常见陷阱。
随着实践的深入,你会发现异步编程不再是障碍,而是打开新世界大门的钥匙。它让你能够优雅地处理网络请求、文件操作、定时任务等各种场景,创造出真正专业级的 JSBox 应用。