乐闻世界logo
搜索文章和话题

服务端面试题手册

如何优化 Cheerio 的性能?有哪些性能优化技巧?

Cheerio 本身是一个轻量级的 HTML 解析器,性能已经非常出色,但在处理大量数据或复杂场景时,我们仍然可以通过多种方式进一步优化性能:1. 选择器性能优化使用具体的选择器// ❌ 慢:使用通配符const items = $('*').filter('.item');// ✅ 快:直接选择const items = $('.item');// ❌ 慢:多重后代选择器const items = $('div div div .item');// ✅ 快:更具体的选择器const items = $('.container .item');// ❌ 慢:使用复杂伪类const items = $('div:has(p):not(.hidden)');// ✅ 快:简化选择器const items = $('div.active');缓存选择器结果// ❌ 慢:重复查询for (let i = 0; i < 100; i++) { const title = $('.item').eq(i).find('.title').text();}// ✅ 快:缓存查询结果const $items = $('.item');for (let i = 0; i < $items.length; i++) { const title = $items.eq(i).find('.title').text();}使用 find() 代替层级选择器// ❌ 较慢const items = $('.container .item .title');// ✅ 更快const $container = $('.container');const items = $container.find('.item').find('.title');2. DOM 操作优化批量操作而非逐个操作// ❌ 慢:逐个添加元素for (let i = 0; i < 1000; i++) { $('.container').append(`<div class="item">${i}</div>`);}// ✅ 快:批量构建 HTMLlet html = '';for (let i = 0; i < 1000; i++) { html += `<div class="item">${i}</div>`;}$('.container').html(html);// ✅ 更快:使用数组 joinconst items = Array.from({ length: 1000 }, (_, i) => `<div class="item">${i}</div>`).join('');$('.container').html(items);减少重排和重绘// ❌ 慢:多次修改 DOM$('.item').addClass('active');$('.item').css('color', 'red');$('.item').attr('data-id', '123');// ✅ 快:一次性修改$('.item').addClass('active').css('color', 'red').attr('data-id', '123');使用文档片段(对于大量插入)// 对于大量 DOM 插入,先构建完整 HTML 再插入function buildLargeList(data) { const html = data.map(item => ` <li class="item" data-id="${item.id}"> <span class="title">${item.title}</span> <span class="price">${item.price}</span> </li> `).join(''); return cheerio.load(`<ul>${html}</ul>`);}3. 数据提取优化使用原生方法获取数据// ❌ 慢:使用 Cheerio 方法const texts = [];$('.item').each((i, el) => { texts.push($(el).text());});// ✅ 快:使用原生方法const texts = $('.item').map((i, el) => el.textContent).get();// ✅ 更快:直接遍历 DOM 元素const $items = $('.item');const texts = [];for (let i = 0; i < $items.length; i++) { texts.push($items[i].textContent);}优化 map() 和 each() 的使用// ❌ 慢:在 each 中创建新对象const data = [];$('.item').each((i, el) => { data.push({ title: $(el).find('.title').text(), price: $(el).find('.price').text() });});// ✅ 快:使用 map()const data = $('.item').map((i, el) => ({ title: $(el).find('.title').text(), price: $(el).find('.price').text()})).get();4. 内存管理优化及时释放大对象// 处理大文件时,分批处理function processLargeHtml(html) { const $ = cheerio.load(html); const batchSize = 1000; const total = $('.item').length; const results = []; for (let i = 0; i < total; i += batchSize) { const $batch = $('.item').slice(i, i + batchSize); const batchData = $batch.map((j, el) => ({ id: $(el).attr('data-id'), title: $(el).find('.title').text() })).get(); results.push(...batchData); // 及时清理 $batch = null; } return results;}避免内存泄漏// ❌ 可能导致内存泄漏let $ = cheerio.load(html);// ... 处理// 忘记清理 $// ✅ 及时清理function processHtml(html) { const $ = cheerio.load(html); const result = extractData($); // Cheerio 对象会自动被垃圾回收 return result;}5. 并发处理优化使用 Worker 线程处理大量数据const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');if (isMainThread) { // 主线程 async function processInParallel(htmlChunks) { const workers = htmlChunks.map(chunk => new Promise((resolve) => { const worker = new Worker(__filename, { workerData: chunk }); worker.on('message', resolve); }) ); return Promise.all(workers); }} else { // Worker 线程 const cheerio = require('cheerio'); const $ = cheerio.load(workerData); const result = extractData($); parentPort.postMessage(result);}批量处理 URLconst axios = require('axios');const cheerio = require('cheerio');async function batchScrape(urls, concurrency = 5) { const results = []; for (let i = 0; i < urls.length; i += concurrency) { const batch = urls.slice(i, i + concurrency); const batchResults = await Promise.all( batch.map(url => scrapeUrl(url)) ); results.push(...batchResults); } return results;}async function scrapeUrl(url) { const response = await axios.get(url); const $ = cheerio.load(response.data); return extractData($);}6. 配置优化使用合适的加载选项// ✅ 禁用不必要的功能以提高性能const $ = cheerio.load(html, { // 不解码 HTML 实体(如果不需要) decodeEntities: false, // 不包含空白节点 withDomLvl1: false, // 不规范化空白 normalizeWhitespace: false});XML 模式优化// 处理 XML 时使用 XML 模式const $ = cheerio.load(xml, { xmlMode: true, decodeEntities: false});7. 性能监控和测试性能测试工具function benchmark(fn, iterations = 1000) { const start = process.hrtime.bigint(); for (let i = 0; i < iterations; i++) { fn(); } const end = process.hrtime.bigint(); const duration = Number(end - start) / 1000000; // 转换为毫秒 return { total: duration, average: duration / iterations, perSecond: iterations / (duration / 1000) };}// 使用示例const result = benchmark(() => { const $ = cheerio.load(html); $('.item').text();}, 1000);console.log(`平均耗时: ${result.average}ms`);console.log(`每秒处理: ${result.perSecond} 次`);内存使用监控function getMemoryUsage() { const usage = process.memoryUsage(); return { rss: `${Math.round(usage.rss / 1024 / 1024)} MB`, heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)} MB`, heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)} MB` };}// 使用示例console.log('处理前:', getMemoryUsage());const result = processLargeHtml(html);console.log('处理后:', getMemoryUsage());8. 实际优化案例优化前async function scrapeSlow(urls) { const results = []; for (const url of urls) { const response = await axios.get(url); const $ = cheerio.load(response.data); $('.item').each((i, el) => { results.push({ title: $(el).find('.title').text(), price: $(el).find('.price').text(), description: $(el).find('.description').text() }); }); } return results;}优化后async function scrapeFast(urls) { // 并发请求 const responses = await Promise.all( urls.map(url => axios.get(url)) ); // 批量处理 const results = responses.flatMap(response => { const $ = cheerio.load(response.data); return $('.item').map((i, el) => ({ title: $(el).find('.title').text(), price: $(el).find('.price').text(), description: $(el).find('.description').text() })).get(); }); return results;}总结Cheerio 性能优化的关键点:选择器优化:使用具体、高效的选择器,缓存查询结果DOM 操作优化:批量操作,减少重排重绘数据提取优化:使用原生方法,优化 map/each内存管理:及时释放大对象,避免内存泄漏并发处理:合理使用并发,提高吞吐量配置优化:根据需求调整加载选项性能监控:定期测试和监控性能指标通过这些优化,Cheerio 可以处理数百万级别的 DOM 元素,保持出色的性能表现。
阅读 0·2月22日 14:30

Dubbo 框架的核心架构和特性是什么?Dubbo 如何实现服务治理?

Dubbo 是阿里巴巴开源的高性能 Java RPC 框架,广泛应用于微服务架构中:核心架构:1. 服务提供者(Provider)暴露服务的应用启动时向注册中心注册服务可以部署多个实例实现负载均衡2. 服务消费者(Consumer)调用远程服务的应用启动时从注册中心订阅服务通过代理调用远程服务3. 注册中心(Registry)服务注册与发现的核心组件常用实现:Zookeeper、Nacos、Redis负责维护服务列表和健康状态4. 监控中心(Monitor)统计服务调用次数和调用时间提供服务治理数据支持常用实现:Dubbo Admin、Prometheus5. 容器(Container)服务运行容器常用:Spring Container、Spring Boot核心特性:1. 远程调用支持多种协议:Dubbo、RMI、Hessian、HTTP、Webservice、Thrift、REST默认使用 Dubbo 协议(基于 Netty)支持同步和异步调用2. 集群容错Failover:失败自动切换,默认策略Failfast:快速失败,只发起一次调用Failsafe:失败安全,出现异常时忽略Failback:失败自动恢复,后台记录失败请求Forking:并行调用,只要一个成功即返回Broadcast:广播调用,所有调用都成功才算成功3. 负载均衡Random:随机,按权重设置随机概率RoundRobin:轮询,按公约后的权重设置轮询比率LeastActive:最少活跃调用数ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者4. 服务降级Mock 数据返回 null抛出指定异常5. 服务限流并发数限制QPS 限制6. 服务路由条件路由标签路由脚本路由7. 配置中心动态配置配置版本管理配置推送使用示例:服务提供者:@Servicepublic class UserServiceImpl implements UserService { @Override public User getUserById(Long id) { return new User(id, "张三"); }}// 配置<dubbo:service interface="com.example.UserService" ref="userService"/>服务消费者:// 配置<dubbo:reference interface="com.example.UserService" id="userService"/>// 使用@Autowiredprivate UserService userService;public void test() { User user = userService.getUserById(1L);}优势:高性能:基于 Netty,支持长连接易用性:与 Spring 深度集成可扩展:支持多种协议和负载均衡策略服务治理:完善的服务治理功能社区活跃:阿里巴巴和社区持续维护适用场景:Java 微服务架构内部服务调用高并发场景需要完善服务治理的系统
阅读 0·2月22日 14:08

Gradle 有哪些常用命令?如何优化构建性能?

Gradle 提供了丰富的命令行工具和选项,熟练掌握这些命令可以大大提高开发效率。以下是 Gradle 常用命令的详细说明:基本命令查看帮助# 查看帮助信息./gradlew help# 查看任务帮助./gradlew help --task build# 查看所有可用任务./gradlew tasks# 查看特定组的任务./gradlew tasks --group=build# 查看所有任务(包括隐藏任务)./gradlew tasks --all查看项目信息# 查看项目信息./gradlew projects# 查看项目属性./gradlew properties# 查看依赖./gradlew dependencies# 查看特定配置的依赖./gradlew dependencies --configuration implementation# 查看特定项目的依赖./gradlew :app:dependencies构建命令基本构建# 构建项目./gradlew build# 清理并构建./gradlew clean build# 跳过测试构建./gradlew build -x test# 只运行测试./gradlew test# 运行特定测试类./gradlew test --tests MyTest# 运行特定测试方法./gradlew test --tests MyTest.testMethod构建变体# 构建特定变体./gradlew assembleDebug./gradlew assembleRelease# 构建所有变体./gradlew assemble# 构建特定模块./gradlew :module1:build./gradlew :module2:build任务执行执行单个任务# 执行特定任务./gradlew clean# 执行多个任务./gradlew clean build test# 执行任务并传递参数./gradlew build -Pprofile=production任务依赖# 查看任务依赖图./gradlew build --dry-run# 查看任务执行顺序./gradlew build --console=plain# 强制重新执行任务./gradlew build --rerun-tasks性能优化命令并行构建# 启用并行构建./gradlew build --parallel# 指定并行线程数./gradlew build --parallel --max-workers=4# 配置按需构建./gradlew build --configure-on-demand构建缓存# 启用构建缓存./gradlew build --build-cache# 清理构建缓存./gradlew cleanBuildCache# 使用离线模式./gradlew build --offline配置缓存# 启用配置缓存./gradlew build --configuration-cache# 清理配置缓存./gradlew cleanConfigurationCache调试和诊断详细输出# 显示详细日志./gradlew build --info# 显示调试日志./gradlew build --debug# 显示堆栈跟踪./gradlew build --stacktrace# 显示完整堆栈跟踪./gradlew build --full-stacktrace性能分析# 生成构建报告./gradlew build --scan# 生成性能报告./gradlew build --profile# 查看构建时间./gradlew build --console=plain依赖分析# 查看依赖树./gradlew :app:dependencies# 查看特定依赖的详细信息./gradlew dependencyInsight --dependency spring-boot-starter-web# 查找依赖冲突./gradlew dependencies | grep -i conflict持续构建文件监控# 启用持续构建./gradlew build --continuous# 指定监控间隔(秒)./gradlew build --continuous --interval=5# 持续测试./gradlew test --continuous自定义任务执行传递参数# 传递项目属性./gradlew build -Pversion=1.0.0# 传递系统属性./gradlew build -Dspring.profiles.active=production# 传递 JVM 参数./gradlew build -Dorg.gradle.jvmargs="-Xmx2048m"# 传递多个参数./gradlew build -Penv=prod -Dlog.level=debug条件执行# 只在特定条件下执行任务./gradlew build -PenableFeature=true# 使用环境变量ENV=production ./gradlew build插件管理查看插件# 查看已应用的插件./gradlew plugins# 查看插件详情./gradlew plugins --detail更新插件# 更新依赖./gradlew dependencyUpdates# 更新 Wrapper./gradlew wrapper --gradle-version=8.0多项目构建项目选择# 构建特定项目./gradlew :app:build# 构建多个项目./gradlew :app:build :library:build# 构建所有项目./gradlew build# 排除特定项目./gradlew build -x :module1:build项目依赖# 查看项目依赖关系./gradlew projects# 查看特定项目的依赖./gradlew :app:dependenciesAndroid 特定命令Android 构建# 构建 Debug 版本./gradlew assembleDebug# 构建 Release 版本./gradlew assembleRelease# 安装到设备./gradlew installDebug./gradlew installRelease# 卸载应用./gradlew uninstallDebug./gradlew uninstallReleaseAndroid 测试# 运行单元测试./gradlew test# 运行仪器测试./gradlew connectedAndroidTest# 运行特定测试./gradlew test --tests com.example.MyTestAndroid 其他命令# 生成 Lint 报告./gradlew lint# 生成签名 APK./gradlew assembleRelease# 生成 Bundle./gradlew bundleRelease发布和部署发布到仓库# 发布到本地仓库./gradlew publishToMavenLocal# 发布到远程仓库./gradlew publish# 发布特定模块./gradlew :app:publish版本管理# 查看版本./gradlew --version# 使用特定版本./gradlew build --gradle-version=8.0常用选项通用选项# 指定设置文件./gradlew build --settings-file=custom-settings.gradle# 指定构建文件./gradlew build --build-file=custom-build.gradle# 指定 Gradle 用户主目录./gradlew build --gradle-user-home=/custom/path# 指定项目目录./gradlew build --project-dir=/custom/project输出控制# 控制台输出模式./gradlew build --console=plain./gradlew build --console=auto./gradlew build --console=rich# 颜色输出./gradlew build --color=always./gradlew build --color=never./gradlew build --color=auto# 安静模式./gradlew build --quiet故障排除清理和重试# 清理构建./gradlew clean# 清理所有缓存./gradlew clean cleanBuildCache cleanConfigurationCache# 强制重新下载依赖./gradlew build --refresh-dependencies# 重新执行所有任务./gradlew build --rerun-tasks网络问题# 使用离线模式./gradlew build --offline# 配置代理./gradlew build -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080最佳实践1. 使用别名# 在 shell 中创建别名alias gb='./gradlew build'alias gt='./gradlew test'alias gc='./gradlew clean'2. 使用脚本# 创建构建脚本#!/bin/bash./gradlew clean build --parallel --build-cache3. 使用配置文件# gradle.propertiesorg.gradle.parallel=trueorg.gradle.caching=trueorg.gradle.configureondemand=trueorg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m4. 使用 Gradle Daemon# 启用 Gradle Daemon./gradlew build --daemon# 停止所有 Daemon./gradlew --stop# 查看运行中的 Daemon./gradlew --status常见问题解决1. 内存不足# 增加 JVM 内存./gradlew build -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=1024m"2. 构建缓慢# 启用并行构建和缓存./gradlew build --parallel --build-cache --configuration-cache3. 依赖冲突# 查看依赖树./gradlew dependencies# 使用依赖分析工具./gradlew dependencyInsight --dependency <dependency-name>4. 任务不执行# 强制重新执行任务./gradlew build --rerun-tasks# 查看任务状态./gradlew build --info
阅读 0·2月22日 14:08

Gradle 如何实现构建变体和多环境配置?

Gradle 支持多种构建变体和产品风味,这对于 Android 开发和多环境部署尤为重要。以下是 Gradle 构建变体的详细说明:构建变体概念构建变体(Build Variants)是 Gradle 中用于生成不同版本应用程序的机制,它允许开发者基于不同的配置生成多个构建输出。Android 构建变体基本配置android { // 构建类型 buildTypes { debug { applicationIdSuffix ".debug" versionNameSuffix "-debug" debuggable true minifyEnabled false } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } // 产品风味 flavorDimensions "version", "environment" productFlavors { free { dimension "version" applicationId "com.example.app.free" versionName "1.0-free" } paid { dimension "version" applicationId "com.example.app.paid" versionName "1.0-paid" } dev { dimension "environment" buildConfigField "String", "API_URL", "\"https://dev.api.example.com\"" resValue "string", "app_name", "My App (Dev)" } staging { dimension "environment" buildConfigField "String", "API_URL", "\"https://staging.api.example.com\"" resValue "string", "app_name", "My App (Staging)" } prod { dimension "environment" buildConfigField "String", "API_URL", "\"https://api.example.com\"" resValue "string", "app_name", "My App" } }}构建变体组合上面的配置会生成以下构建变体:freeDevDebugfreeDevReleasefreeStagingDebugfreeStagingReleasefreeProdDebugfreeProdReleasepaidDevDebugpaidDevReleasepaidStagingDebugpaidStagingReleasepaidProdDebugpaidProdReleaseJava/Kotlin 构建变体使用源集// build.gradlesourceSets { main { java { srcDirs 'src/main/java' } resources { srcDirs 'src/main/resources' } } // 自定义源集 custom { java { srcDirs 'src/custom/java' } resources { srcDirs 'src/custom/resources' } }}// 为特定构建类型配置源集android.sourceSets { debug { java.srcDirs 'src/debug/java' res.srcDirs 'src/debug/res' } release { java.srcDirs 'src/release/java' res.srcDirs 'src/release/res' }}使用任务变体// 为不同环境创建任务tasks.register('buildDev') { group = 'build' description = 'Build for development environment' doLast { // 开发环境构建逻辑 }}tasks.register('buildStaging') { group = 'build' description = 'Build for staging environment' doLast { // 预发布环境构建逻辑 }}tasks.register('buildProd') { group = 'build' description = 'Build for production environment' doLast { // 生产环境构建逻辑 }}多环境配置使用配置文件// build.gradleext { environments = [ dev: [ apiUrl: 'https://dev.api.example.com', dbUrl: 'jdbc:mysql://dev-db.example.com:3306/mydb', enableDebug: true ], staging: [ apiUrl: 'https://staging.api.example.com', dbUrl: 'jdbc:mysql://staging-db.example.com:3306/mydb', enableDebug: false ], prod: [ apiUrl: 'https://api.example.com', dbUrl: 'jdbc:mysql://prod-db.example.com:3306/mydb', enableDebug: false ] ]}// 根据环境变量选择配置def environment = project.hasProperty('env') ? project.env : 'dev'def config = environments[environment]tasks.register('buildWithConfig') { doLast { println "Building for environment: ${environment}" println "API URL: ${config.apiUrl}" println "DB URL: ${config.dbUrl}" println "Debug enabled: ${config.enableDebug}" }}使用属性文件// 创建配置任务tasks.register('generateConfig') { def env = project.hasProperty('env') ? project.env : 'dev' def configFile = file("config/${env}.properties") inputs.file configFile outputs.dir('build/config') doLast { copy { from configFile into 'build/config' rename { 'application.properties' } } }}// 依赖配置任务tasks.named('processResources') { dependsOn 'generateConfig' from 'build/config'}动态变体生成基于输入生成变体// build.gradledef variants = ['variant1', 'variant2', 'variant3']variants.each { variant -> tasks.register("build${variant.capitalize()}") { group = 'build' description = "Build ${variant}" doLast { println "Building ${variant}" // 变体特定的构建逻辑 } }}// 创建聚合任务tasks.register('buildAllVariants') { group = 'build' description = 'Build all variants' dependsOn variants.collect { "build${it.capitalize()}" }}基于配置文件生成变体// variants.json[ { "name": "variant1", "version": "1.0.0", "features": ["feature1", "feature2"] }, { "name": "variant2", "version": "2.0.0", "features": ["feature1", "feature3"] }]// build.gradleimport groovy.json.JsonSlurperdef variantsFile = file('variants.json')def variants = new JsonSlurper().parse(variantsFile)variants.each { variant -> tasks.register("build${variant.name.capitalize()}") { group = 'build' description = "Build ${variant.name} v${variant.version}" doLast { println "Building ${variant.name} version ${variant.version}" println "Features: ${variant.features.join(', ')}" } }}变体特定依赖为不同变体配置依赖// Android 项目dependencies { implementation 'androidx.core:core-ktx:1.9.0' // Debug 特定依赖 debugImplementation 'com.facebook.stetho:stetho:1.6.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' // Release 特定依赖 releaseImplementation 'com.squareup.okhttp3:okhttp:4.10.0' // 产品风味特定依赖 freeImplementation 'com.google.android.gms:play-services-ads:21.3.0' paidImplementation 'com.example:premium-features:1.0.0' // 构建变体特定依赖 freeDevImplementation 'com.example:dev-tools:1.0.0' paidProdImplementation 'com.example:prod-analytics:1.0.0'}Java 项目变体依赖configurations { devImplementation stagingImplementation prodImplementation}dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0' devImplementation 'org.springframework.boot:spring-boot-devtools:3.0.0' stagingImplementation 'org.springframework.boot:spring-boot-starter-actuator:3.0.0' prodImplementation 'org.springframework.boot:spring-boot-starter-security:3.0.0'}// 为不同环境创建任务tasks.register('runDev', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.devImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=dev']}tasks.register('runStaging', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.stagingImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=staging']}tasks.register('runProd', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.prodImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=prod']}变体特定资源Android 资源变体app/├── src/│ ├── main/│ │ ├── res/│ │ │ ├── values/│ │ │ │ └── strings.xml│ │ │ └── drawable/│ │ │ └── icon.png│ ├── debug/│ │ └── res/│ │ └── values/│ │ └── strings.xml│ ├── free/│ │ └── res/│ │ └── values/│ │ └── strings.xml│ └── paid/│ └── res/│ └── values/│ └── strings.xmlJava 资源变体sourceSets { main { resources { srcDirs 'src/main/resources' } } dev { resources { srcDirs 'src/dev/resources' } } staging { resources { srcDirs 'src/staging/resources' } } prod { resources { srcDirs 'src/prod/resources' } }}// 为不同环境创建 JARtasks.register('devJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.dev.output archiveFileName = 'app-dev.jar'}tasks.register('stagingJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.staging.output archiveFileName = 'app-staging.jar'}tasks.register('prodJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.prod.output archiveFileName = 'app-prod.jar'}最佳实践1. 合理规划变体维度// 避免过多的变体组合flavorDimensions "version" // 只使用一个维度productFlavors { free { dimension "version" } paid { dimension "version" }}2. 使用共享配置// 定义共享配置def commonConfig = { versionCode 1 versionName "1.0.0" minSdkVersion 21 targetSdkVersion 33}android { defaultConfig commonConfig}3. 使用变体过滤器android { variantFilter { variant -> def names = variant.flavors*.name if (names.contains("paid") && names.contains("dev")) { variant.setIgnore(true) // 忽略 paidDev 变体 } }}4. 使用构建变体特定的任务android.applicationVariants.all { variant -> def variantName = variant.name.capitalize() tasks.register("process${variantName}Resources") { doLast { println "Processing resources for ${variant.name}" } }}5. 使用版本目录管理变体// gradle/libs.versions.toml[versions]spring-boot = "3.0.0"[libraries]spring-boot-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }// 在 build.gradle 中使用dependencies { implementation libs.spring.boot.web}
阅读 0·2月22日 14:08

gRPC 的核心特性和优势是什么?为什么选择 gRPC 而不是其他 RPC 框架?

gRPC 是 Google 开源的高性能 RPC 框架,基于 HTTP/2 和 Protobuf 构建,具有以下核心特性和优势:核心特性:1. 基于 HTTP/2多路复用:单个 TCP 连接可以同时发送多个请求,减少连接开销二进制分帧:比 HTTP/1.x 的文本格式更高效头部压缩:使用 HPACK 算法压缩头部,减少传输数据量服务端推送:支持服务端主动推送数据流式传输:支持单向流和双向流2. 基于 Protobuf高效序列化:二进制格式,序列化/反序列化速度快强类型:通过 .proto 文件定义接口,编译时类型检查跨语言:支持 10+ 种编程语言向后兼容:字段编号机制保证版本兼容性3. 四种服务模式一元 RPC(Unary):客户端发送一个请求,服务端返回一个响应服务端流式 RPC(Server Streaming):客户端发送一个请求,服务端返回流式响应客户端流式 RPC(Client Streaming):客户端发送流式请求,服务端返回一个响应双向流式 RPC(Bidirectional Streaming):客户端和服务端都可以发送流式数据优势:1. 高性能HTTP/2 多路复用减少连接开销Protobuf 二进制序列化效率高支持流式传输,适合大数据场景2. 低延迟二进制协议减少解析时间多路复用避免队头阻塞连接复用减少握手开销3. 跨语言支持自动生成多种语言的客户端和服务端代码统一的接口定义语言(IDL)无缝集成不同语言的服务4. 强类型和代码生成编译时类型检查,减少运行时错误自动生成代码,提高开发效率IDE 支持良好,开发体验佳5. 流式通信支持实时数据传输适合聊天、推送、实时监控等场景减少请求-响应的往返次数6. 双向流支持客户端和服务端可以同时发送数据适合实时协作、游戏等场景减少连接建立的开销7. 生态系统完善拦截器机制(Interceptor)负载均衡服务发现链路追踪集成适用场景:微服务内部通信实时数据流处理跨语言服务调用高性能要求的场景需要流式通信的应用代码示例:// 定义服务service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}}// 定义消息message HelloRequest { string name = 1;}message HelloReply { string message = 1;}
阅读 0·2月22日 14:08

Promise.any() 的作用是什么?

Promise.any() 是 ES2021 引入的 Promise 静态方法,它接收一个 Promise 数组,返回第一个成功完成的 Promise 的结果。如果所有 Promise 都失败,则返回 AggregateError。基本概念Promise.any() 接收一个可迭代的 Promise 对象作为参数,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 成功完成时立即完成,返回该 Promise 的结果。如果所有 Promise 都失败,则返回一个 AggregateError,包含所有失败的原因。基本用法const promise1 = Promise.reject('错误1');const promise2 = Promise.reject('错误2');const promise3 = Promise.resolve('成功');Promise.any([promise1, promise2, promise3]) .then(result => { console.log(result); // 输出: 成功 }) .catch(error => { console.error(error); });与其他方法的对比Promise.any() vs Promise.race()Promise.any(): 返回第一个成功的 Promiseconst promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.resolve('另一个成功');Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功Promise.race(): 返回第一个完成的 Promise(无论成功或失败)const promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.resolve('另一个成功');Promise.race([promise1, promise2, promise3]) .then(result => console.log(result)) .catch(error => console.error(error)); // 输出: 错误1Promise.any() vs Promise.all()Promise.any(): 只要有一个成功就返回const promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.reject('错误3');Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功Promise.all(): 必须全部成功才返回const promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.reject('错误3');Promise.all([promise1, promise2, promise3]) .then(result => console.log(result)) .catch(error => console.error(error)); // 输出: 错误1Promise.any() vs Promise.allSettled()Promise.any(): 返回第一个成功的结果const promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.reject('错误3');Promise.any([promise1, promise2, promise3]) .then(result => console.log(result)); // 输出: 成功Promise.allSettled(): 返回所有 Promise 的状态const promise1 = Promise.reject('错误1');const promise2 = Promise.resolve('成功');const promise3 = Promise.reject('错误3');Promise.allSettled([promise1, promise2, promise3]) .then(results => console.log(results));// 输出:// [// { status: 'rejected', reason: '错误1' },// { status: 'fulfilled', value: '成功' },// { status: 'rejected', reason: '错误3' }// ]AggregateError当所有 Promise 都失败时,Promise.any() 会返回一个 AggregateError,包含所有失败的原因。基本用法const promise1 = Promise.reject('错误1');const promise2 = Promise.reject('错误2');const promise3 = Promise.reject('错误3');Promise.any([promise1, promise2, promise3]) .catch(error => { console.error(error instanceof AggregateError); // true console.error(error.message); // All promises were rejected console.error(error.errors); // ['错误1', '错误2', '错误3'] });处理 AggregateErrorasync function fetchFromMultipleSources(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了:'); error.errors.forEach((err, index) => { console.error(` ${urls[index]}: ${err.message}`); }); throw new Error('无法从任何数据源获取数据'); } throw error; }}实际应用场景1. 从多个数据源获取数据,使用最快成功的async function fetchFromFastestSource(sources) { try { const response = await Promise.any( sources.map(source => fetch(source.url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; }}// 使用示例const sources = [ { name: '主服务器', url: 'https://api1.example.com/data' }, { name: '备用服务器1', url: 'https://api2.example.com/data' }, { name: '备用服务器2', url: 'https://api3.example.com/data' }];fetchFromFastestSource(sources) .then(data => console.log('数据:', data)) .catch(error => console.error(error));2. 实现超时机制function fetchWithTimeout(url, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); return Promise.any([ fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }), timeoutPromise ]);}// 使用示例fetchWithTimeout('/api/data', 3000) .then(data => console.log('数据:', data)) .catch(error => { if (error.message === 'Timeout') { console.error('请求超时'); } else { console.error('请求失败:', error); } });3. 尝试多个备份方案async function tryMultipleStrategies(strategies) { try { return await Promise.any( strategies.map(strategy => strategy()) ); } catch (error) { if (error instanceof AggregateError) { console.error('所有策略都失败了'); throw new Error('无法完成任务'); } throw error; }}// 使用示例const strategies = [ () => fetch('/api/v1/data').then(r => r.json()), () => fetch('/api/v2/data').then(r => r.json()), () => fetch('/api/v3/data').then(r => r.json())];tryMultipleStrategies(strategies) .then(data => console.log('数据:', data)) .catch(error => console.error(error));4. 图片加载,使用第一个成功加载的async function loadFirstSuccessfulImage(imageUrls) { try { const imagePromises = imageUrls.map(url => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Failed to load: ${url}`)); img.src = url; }); }); return await Promise.any(imagePromises); } catch (error) { if (error instanceof AggregateError) { console.error('所有图片都加载失败'); throw new Error('无法加载图片'); } throw error; }}// 使用示例const imageUrls = [ 'https://cdn1.example.com/image.jpg', 'https://cdn2.example.com/image.jpg', 'https://cdn3.example.com/image.jpg'];loadFirstSuccessfulImage(imageUrls) .then(img => document.body.appendChild(img)) .catch(error => console.error(error));错误处理处理所有失败的情况async function fetchWithFallback(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有 URL 都失败了'); // 可以返回默认值或重新抛出错误 return { error: 'All sources failed' }; } throw error; }}记录所有失败的原因async function fetchWithLogging(urls) { try { const response = await Promise.any( urls.map(url => fetch(url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有 URL 都失败了:'); error.errors.forEach((err, index) => { console.error(` ${urls[index]}: ${err.message}`); }); throw new Error('无法获取数据'); } throw error; }}性能考虑空数组处理Promise.any([]) .then(result => console.log(result)) .catch(error => { console.error(error instanceof AggregateError); // true console.error(error.errors); // [] });非 Promise 值处理Promise.any([1, 2, Promise.resolve(3)]) .then(result => console.log(result)); // 输出: 1浏览器兼容性Promise.any() 是 ES2021 引入的,现代浏览器都支持:Chrome: 85+Firefox: 79+Safari: 14+Edge: 85+对于旧浏览器,可以使用 polyfill:if (!Promise.any) { Promise.any = function(promises) { return new Promise((resolve, reject) => { const errors = []; let rejectedCount = 0; promises.forEach((promise, index) => { Promise.resolve(promise).then( value => resolve(value), error => { errors[index] = error; rejectedCount++; if (rejectedCount === promises.length) { reject(new AggregateError(errors, 'All promises were rejected')); } } ); }); if (promises.length === 0) { reject(new AggregateError(errors, 'All promises were rejected')); } }); };}最佳实践1. 总是处理 AggregateErrorasync function fetchData(sources) { try { return await Promise.any(sources.map(s => fetch(s))); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; }}2. 提供降级方案async function fetchWithFallback(sources, fallbackData) { try { const response = await Promise.any(sources.map(s => fetch(s))); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.warn('所有数据源都失败,使用降级数据'); return fallbackData; } throw error; }}3. 记录失败原因async function fetchWithLogging(sources) { try { return await Promise.any(sources.map(s => fetch(s))); } catch (error) { if (error instanceof AggregateError) { error.errors.forEach((err, index) => { console.error(`${sources[index]} 失败:`, err.message); }); throw new Error('无法获取数据'); } throw error; }}总结返回第一个成功的 Promise:只要有一个成功就立即返回AggregateError 处理所有失败:所有 Promise 都失败时返回 AggregateError适合多数据源场景:从多个数据源获取数据,使用最快成功的与 Promise.race() 不同:只返回成功的结果,不返回失败的结果与 Promise.all() 不同:不需要全部成功,只要一个成功即可提供降级方案:所有失败时可以提供降级数据记录失败原因:AggregateError 包含所有失败的原因
阅读 0·2月22日 14:07

如何优化 RPC 调用的性能?有哪些减少网络延迟的方法?

RPC 调用中,网络延迟和性能优化是关键问题,需要从多个层面进行优化:1. 连接池优化长连接复用:避免频繁建立和断开连接连接池大小:根据并发量合理配置连接池连接预热:启动时预先建立连接连接保活:定期发送心跳保持连接活跃实现示例: // Netty 连接池配置 EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true);2. 序列化优化选择高效序列化协议:Protobuf > Thrift > Hessian > JSON减少序列化数据量:使用字段编号而非字段名避免序列化不必要的数据使用压缩算法(如 Gzip、Snappy)对象池技术:复用序列化对象,减少 GC 压力3. 网络传输优化TCP 参数调优:TCP_NODELAY:禁用 Nagle 算法,减少延迟SO_KEEPALIVE:启用 TCP 保活SO_RCVBUF/SO_SNDBUF:调整接收和发送缓冲区大小HTTP/2 多路复用:减少连接数,提高并发性能批量请求:合并多个小请求,减少网络往返4. 负载均衡优化就近原则:选择网络延迟最低的服务实例权重分配:根据实例性能分配不同权重健康检查:快速剔除故障实例实现示例: // Dubbo 权重负载均衡 <dubbo:reference loadbalance="random"/>5. 缓存策略客户端缓存:缓存频繁调用的结果服务端缓存:缓存计算结果,减少重复计算分布式缓存:使用 Redis 等分布式缓存缓存失效:合理设置缓存过期时间6. 异步调用非阻塞调用:避免线程阻塞等待响应Future/Promise:异步获取结果响应式编程:使用 RxJava、Reactor 等实现示例: // gRPC 异步调用 stub.sayHello(request, new StreamObserver<HelloResponse>() { @Override public void onNext(HelloResponse response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } });7. 代码优化减少不必要的字段:只传输必要的数据使用基本类型:避免使用包装类型避免大对象传输:分批传输大数据压缩传输:启用数据压缩8. 监控和调优性能监控:监控调用耗时、成功率、QPS链路追踪:使用 Zipkin、Jaeger 追踪调用链日志分析:分析慢调用日志性能测试:定期进行压力测试9. 服务端优化线程池优化:合理配置线程池大小I/O 模型:使用 Netty 等高性能 I/O 框架零拷贝:使用 FileChannel.transferTo 减少数据拷贝JVM 调优:优化 GC 参数10. 架构优化服务拆分:合理拆分服务,减少单个服务负载读写分离:分离读写操作,提高并发能力CDN 加速:静态资源使用 CDN边缘计算:将计算下沉到边缘节点性能指标:P99 延迟:99% 请求的响应时间QPS:每秒查询数TPS:每秒事务数吞吐量:单位时间处理的数据量
阅读 0·2月22日 14:07

Promise.allSettled() 的作用是什么?与 Promise.all() 有什么区别?

Promise.allSettled() 是 ES2020 引入的 Promise 静态方法,它允许我们等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的状态和结果。基本概念Promise.allSettled() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 会在所有输入的 Promise 都完成(无论成功或失败)后才完成,返回一个包含所有 Promise 状态和结果的数组。基本用法const promise1 = Promise.resolve(42);const promise2 = Promise.reject('出错了');const promise3 = new Promise(resolve => setTimeout(() => resolve('延迟完成'), 1000));Promise.allSettled([promise1, promise2, promise3]) .then(results => { console.log(results); // 输出: // [ // { status: 'fulfilled', value: 42 }, // { status: 'rejected', reason: '出错了' }, // { status: 'fulfilled', value: '延迟完成' } // ] });返回值结构每个结果对象包含两个属性:status: 'fulfilled'(成功)或 'rejected'(失败)value: 成功时的值(仅当 status 为 'fulfilled' 时存在)reason: 失败时的原因(仅当 status 为 'rejected' 时存在)与 Promise.all() 的对比Promise.all() - 快速失败const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error('错误:', error)); // 输出: 错误: 错误 // promise3 的结果无法获取Promise.allSettled() - 等待全部const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.error('失败:', result.reason); } }); }); // 输出: // 成功: 1 // 失败: 错误 // 成功: 3实际应用场景1. 批量请求,部分失败不影响其他结果async function fetchMultipleUrls(urls) { const promises = urls.map(url => fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) ); const results = await Promise.allSettled(promises); const successful = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failed = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.log('成功的请求:', successful.length); console.log('失败的请求:', failed.length); return { successful, failed };}// 使用示例const urls = [ 'https://api.example.com/users', 'https://api.example.com/posts', 'https://api.example.com/comments'];fetchMultipleUrls(urls).then(({ successful, failed }) => { console.log('成功数据:', successful); console.log('失败原因:', failed);});2. 并行处理多个任务,收集所有结果async function processTasks(tasks) { const results = await Promise.allSettled( tasks.map(task => task()) ); return results.map((result, index) => ({ task: tasks[index].name, status: result.status, result: result.status === 'fulfilled' ? result.value : result.reason }));}// 使用示例const tasks = [ { name: '任务1', fn: () => Promise.resolve('完成') }, { name: '任务2', fn: () => Promise.reject('失败') }, { name: '任务3', fn: () => Promise.resolve('完成') }];processTasks(tasks).then(results => { console.table(results);});3. 图片加载,显示成功和失败的图片async function loadImages(imageUrls) { const imagePromises = imageUrls.map(url => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve({ url, img }); img.onerror = () => reject(new Error(`Failed to load: ${url}`)); img.src = url; }); }); const results = await Promise.allSettled(imagePromises); const loadedImages = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedUrls = results .filter(result => result.status === 'rejected') .map(result => result.reason.message); return { loadedImages, failedUrls };}// 使用示例const imageUrls = [ 'https://example.com/image1.jpg', 'https://example.com/image2.jpg', 'https://example.com/image3.jpg'];loadImages(imageUrls).then(({ loadedImages, failedUrls }) => { console.log('加载成功的图片:', loadedImages); console.log('加载失败的图片:', failedUrls);});错误处理处理部分失败async function fetchWithPartialFailure(urls) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const successfulRequests = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedRequests = results .filter(result => result.status === 'rejected') .map(result => result.reason); if (failedRequests.length > 0) { console.warn('部分请求失败:', failedRequests); } return successfulRequests;}重试失败的请求async function fetchWithRetry(urls, maxRetries = 3) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const failedUrls = results .filter(result => result.status === 'rejected') .map((result, index) => urls[index]); if (failedUrls.length > 0) { console.log(`重试失败的请求 (${maxRetries} 次)`); const retryResults = await Promise.allSettled( failedUrls.map(url => fetch(url)) ); // 处理重试结果... } return results;}性能考虑1. 空数组处理Promise.allSettled([]) .then(results => console.log(results)); // 输出: []2. 非 Promise 值处理Promise.allSettled([1, 2, Promise.resolve(3)]) .then(results => console.log(results)); // 输出: // [ // { status: 'fulfilled', value: 1 }, // { status: 'fulfilled', value: 2 }, // { status: 'fulfilled', value: 3 } // ]与其他 Promise 方法的对比| 方法 | 行为 | 使用场景 ||------|------|----------|| Promise.all() | 全部成功才成功,一个失败就失败 | 需要所有结果都成功 || Promise.allSettled() | 等待全部完成,返回所有结果 | 需要知道每个操作的结果 || Promise.race() | 返回第一个完成的结果 | 需要最快的结果 || Promise.any() | 返回第一个成功的结果 | 需要任何一个成功的结果 |最佳实践1. 检查所有结果async function checkAllResults(promises) { const results = await Promise.allSettled(promises); const hasFailures = results.some(result => result.status === 'rejected'); if (hasFailures) { const failures = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.error('部分操作失败:', failures); } return results;}2. 聚合结果async function aggregateResults(promises) { const results = await Promise.allSettled(promises); return { total: results.length, successful: results.filter(r => r.status === 'fulfilled').length, failed: results.filter(r => r.status === 'rejected').length, results: results };}3. 提供降级方案async function fetchWithFallback(urls, fallbackData) { const results = await Promise.allSettled( urls.map(url => fetch(url).then(r => r.json())) ); const data = results.map((result, index) => { if (result.status === 'fulfilled') { return result.value; } else { console.warn(`请求 ${urls[index]} 失败,使用降级数据`); return fallbackData[index]; } }); return data;}浏览器兼容性Promise.allSettled() 是 ES2020 引入的,现代浏览器都支持:Chrome: 76+Firefox: 71+Safari: 13+Edge: 79+对于旧浏览器,可以使用 polyfill:if (!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all( promises.map(promise => Promise.resolve(promise).then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }) ) ) ); };}总结等待所有 Promise 完成:无论成功或失败,都会等待所有 Promise 完成返回详细结果:每个 Promise 的状态和结果都会被返回适合部分失败场景:当某些操作失败不影响其他操作时使用与 Promise.all() 互补:Promise.all() 快速失败,Promise.allSettled() 等待全部提供更好的错误处理:可以针对每个失败的操作进行单独处理支持 polyfill:旧浏览器可以通过 polyfill 实现兼容
阅读 0·2月22日 14:07

Promise 和回调函数的区别是什么?

Promise 和回调函数(Callback)都是 JavaScript 中处理异步操作的方式,但它们在设计理念、使用方式和代码可读性上有显著差异。回调函数基本概念回调函数是将一个函数作为参数传递给另一个函数,在异步操作完成后被调用。基本用法function fetchData(callback) { setTimeout(() => { const data = { name: 'John', age: 30 }; callback(null, data); }, 1000);}fetchData((error, data) => { if (error) { console.error('出错了:', error); return; } console.log('数据:', data);});回调地狱问题// 回调地狱:代码嵌套过深,难以维护fetch('/api/user', (error, userResponse) => { if (error) { console.error(error); return; } userResponse.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, postsResponse) => { if (error) { console.error(error); return; } postsResponse.json((error, posts) => { if (error) { console.error(error); return; } console.log('用户文章:', posts); }); }); });});Promise基本概念Promise 是一个代表异步操作最终完成或失败的对象,提供了一种更优雅的方式来处理异步操作。基本用法function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = { name: 'John', age: 30 }; resolve(data); }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('出错了:', error));链式调用// Promise 链式调用:代码扁平,易于阅读fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log('用户文章:', posts)) .catch(error => console.error('出错了:', error));主要区别1. 代码可读性回调函数:// 嵌套过深,难以阅读doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); });});Promise:// 扁平清晰,易于阅读doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));2. 错误处理回调函数:// 错误处理分散,容易遗漏function fetchData(callback) { setTimeout(() => { if (Math.random() > 0.5) { callback(new Error('请求失败')); } else { callback(null, { data: 'success' }); } }, 1000);}fetchData((error, data) => { if (error) { console.error('错误:', error); return; } console.log('数据:', data);});Promise:// 错误处理集中,易于管理function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject(new Error('请求失败')); } else { resolve({ data: 'success' }); } }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('错误:', error));3. 状态管理回调函数:没有明确的状态概念回调可能被多次调用难以追踪异步操作的状态Promise:有明确的三种状态:pending、fulfilled、rejected状态一旦改变就不可逆可以随时查询 Promise 的状态const promise = new Promise((resolve) => { setTimeout(() => resolve('完成'), 1000);});console.log(promise); // Promise {<pending>}setTimeout(() => { console.log(promise); // Promise {<fulfilled>: '完成'}}, 1500);4. 并行处理回调函数:// 难以并行处理多个异步操作function fetchAllData(callback) { let completed = 0; const results = []; const urls = ['/api/user', '/api/posts', '/api/comments']; urls.forEach((url, index) => { fetch(url, (error, data) => { if (error) { callback(error); return; } results[index] = data; completed++; if (completed === urls.length) { callback(null, results); } }); });}Promise:// 轻松并行处理多个异步操作Promise.all([ fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments')]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(results => console.log('所有数据:', results)) .catch(error => console.error('错误:', error));5. 组合和复用回调函数:// 难以组合和复用function fetchUser(id, callback) { setTimeout(() => callback(null, { id, name: 'John' }), 1000);}function fetchPosts(userId, callback) { setTimeout(() => callback(null, [{ id: 1, title: 'Post 1' }]), 1000);}// 组合使用需要嵌套fetchUser(1, (error, user) => { if (error) return; fetchPosts(user.id, (error, posts) => { if (error) return; console.log({ user, posts }); });});Promise:// 易于组合和复用function fetchUser(id) { return Promise.resolve({ id, name: 'John' });}function fetchPosts(userId) { return Promise.resolve([{ id: 1, title: 'Post 1' }]);}// 组合使用清晰明了fetchUser(1) .then(user => Promise.all([user, fetchPosts(user.id)])) .then(([user, posts]) => console.log({ user, posts }));转换关系回转 Promise// 将回调函数转换为 Promisefunction promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); };}// 使用示例const readFile = promisify(fs.readFile);readFile('file.txt') .then(data => console.log(data)) .catch(error => console.error(error));Promise 转回调// 将 Promise 转换为回调函数function callbackify(promiseFn) { return function(...args) { const callback = args.pop(); promiseFn(...args) .then(result => callback(null, result)) .catch(error => callback(error)); };}// 使用示例const fetchDataCallback = callbackify(fetchData);fetchDataCallback((error, data) => { if (error) { console.error(error); return; } console.log(data);});性能对比内存占用回调函数:内存占用较小不需要创建额外的 Promise 对象Promise:内存占用略高每个 Promise 都是一个对象,需要额外的内存执行效率回调函数:执行效率略高没有额外的 Promise 对象创建和微任务调度Promise:执行效率略低需要创建 Promise 对象和调度微任务实际影响// 回调函数console.time('callback');for (let i = 0; i < 10000; i++) { setTimeout(() => {}, 0);}console.timeEnd('callback');// Promiseconsole.time('promise');for (let i = 0; i < 10000; i++) { Promise.resolve();}console.timeEnd('promise');使用场景适合使用回调函数的场景Node.js 核心模块:fs、http 等模块使用回调函数一次性简单操作:简单的异步操作不需要 Promise 的复杂性性能敏感场景:需要极致性能的场景// Node.js 文件读取fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data);});适合使用 Promise 的场景复杂的异步流程:多个异步操作需要组合需要错误处理:需要集中处理错误现代 JavaScript 开发:async/await 语法糖前端开发:fetch API、现代浏览器 API// 使用 async/awaitasync function fetchData() { try { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error('错误:', error); throw error; }}总结| 特性 | 回调函数 | Promise ||------|----------|---------|| 代码可读性 | 容易产生回调地狱 | 链式调用,代码扁平 || 错误处理 | 分散,容易遗漏 | 集中,易于管理 || 状态管理 | 无明确状态 | 三种明确状态 || 并行处理 | 需要手动管理 | Promise.all 等方法 || 组合复用 | 难以组合 | 易于组合 || 性能 | 略高 | 略低 || 内存占用 | 较小 | 略高 || 现代支持 | 传统方式 | 现代标准 |最佳实践优先使用 Promise:在现代 JavaScript 开发中,优先使用 Promise使用 async/await:让异步代码更像同步代码处理错误:总是添加错误处理避免嵌套:保持代码扁平合理使用工具:使用 Promise.all、Promise.race 等方法考虑性能:在性能敏感的场景,可以考虑回调函数
阅读 0·2月22日 14:07