JavaScript系列03-异步编程全解析

本文介绍了异步相关的内容,包括:

  1. 回调函数与回调地狱
  2. Promise详解
  3. async/await语法
  4. Generator函数
  5. 事件循环机制
  6. 异步编程最佳实践

1、回调函数与回调地狱

JavaScript最初是为处理网页交互而设计的语言,异步编程是其核心特性之一。最早的异步编程方式是使用回调函数。

什么是回调函数?

回调函数是作为参数传递给另一个函数的函数,并在特定事件发生后执行。


// 基本回调示例function fetchData(callback) {setTimeout(() => {const data = { name: "张三", age: 30 };callback(data);}, 1000);}fetchData(function(data) {console.log("数据获取成功:", data);});

回调函数使我们能够非阻塞地执行代码,这对于网络请求、文件操作等耗时任务尤为重要。

回调地狱问题

当多个异步操作需要依次执行时,回调函数会嵌套在一起,形成所谓的"回调地狱"(Callback Hell):


fetchUserData(function(user) {console.log("获取用户信息:", user);fetchUserPosts(user.id, function(posts) {console.log("获取用户文章:", posts);fetchPostComments(posts[0].id, function(comments) {console.log("获取文章评论:", comments);fetchCommentAuthor(comments[0].authorId, function(author) {console.log("获取评论作者:", author);// 还可以继续嵌套...});});});});

回调地狱带来的问题:

  • 代码可读性差,形成"金字塔"结构

  • 错误处理复杂

  • 变量作用域容易混淆

  • 代码维护困难

2、Promise详解

Promise是JavaScript中解决回调地狱的第一个标准方案,ES6正式将其纳入规范。

Promise的基本概念

Promise是一个代表异步操作最终完成或失败的对象。它有三种状态:

  • pending: 初始状态,既未完成也未失败

  • fulfilled: 操作成功完成

  • rejected: 操作失败

一旦Promise状态改变,就不能再变,这就是"承诺"的含义。


// 创建Promiseconst promise = new Promise((resolve, reject) => {// 异步操作setTimeout(() => {const success = Math.random() > 0.5;if (success) {resolve("操作成功"); // 成功时调用} else {reject("操作失败"); // 失败时调用}}, 1000);});// 使用Promisepromise.then(result => {console.log(result); // "操作成功"}).catch(error => {console.log(error); // "操作失败"}).finally(() => {console.log("无论成功失败都会执行");});

Promise链式调用

Promise的优势之一是支持链式调用,可以优雅地处理依赖于前一个异步操作结果的多个异步操作:


fetchUserData(userId).then(user => {console.log("用户数据:", user);return fetchUserPosts(user.id); // 返回新的Promise}).then(posts => {console.log("用户文章:", posts);return fetchPostComments(posts[0].id);}).then(comments => {console.log("文章评论:", comments);return fetchCommentAuthor(comments[0].authorId);}).then(author => {console.log("评论作者:", author);}).catch(error => {// 捕获链中任何位置的错误console.error("发生错误:", error);});

这种链式写法将原本嵌套的回调拍平,提高了代码的可读性。

Promise常用方法

Promise类提供了几个实用的静态方法:

Promise.all()

Promise.all(): 并行执行多个Promise,当所有Promise都成功时返回结果数组


// 同时发起多个请求const promises = [fetch('https://api.example.com/users'),fetch('https://api.example.com/posts'),fetch('https://api.example.com/comments')];Promise.all(promises).then(responses => Promise.all(responses.map(res => res.json()))).then(data => {const [users, posts, comments] = data;console.log(users, posts, comments);}).catch(error => {// 任一请求失败都会进入catchconsole.error("至少有一个请求失败:", error);});

Promise.race()

Promise.race(): 返回最先解决或拒绝的Promise结果


// 实现超时功能function fetchWithTimeout(url, ms) {const fetchPromise = fetch(url);const timeoutPromise = new Promise((_, reject) => {setTimeout(() => reject(new Error("请求超时")), ms);});return Promise.race([fetchPromise, timeoutPromise]);}fetchWithTimeout('https://api.example.com/data', 3000).then(response => response.json()).then(data => console.log(data)).catch(error => console.error(error));

Promise.allSettled()

Promise.allSettled(): ES2020引入,等待所有Promise完成(无论成功或失败)


Promise.allSettled([Promise.resolve(1),Promise.reject('错误'),Promise.resolve(3)]).then(results => {console.log(results);// [// { status: "fulfilled", value: 1 },// { status: "rejected", reason: "错误" },// { status: "fulfilled", value: 3 }// ]});

Promise.any()

Promise.any(): ES2021引入,返回首个成功的Promise结果


// 尝试从多个源获取数据,返回最先成功的Promise.any([fetch('https://api-1.example.com/data').then(r => r.json()),fetch('https://api-2.example.com/data').then(r => r.json()),fetch('https://api-3.example.com/data').then(r => r.json())]).then(data => console.log("获取到数据:", data)).catch(errors => console.error("所有请求均失败:", errors));

4、Generator函数

Generator函数是ES6引入的新特性,它允许函数在执行过程中暂停和恢复,这使得它特别适合实现异步控制流。

Generator基础

Generator函数在声明时使用星号(*)标记,内部使用yield关键字暂停执行:


function* numberGenerator() {yield 1;yield 2;yield 3;}const gen = numberGenerator();console.log(gen.next()); // { value: 1, done: false }console.log(gen.next()); // { value: 2, done: false }console.log(gen.next()); // { value: 3, done: false }console.log(gen.next()); // { value: undefined, done: true }

生成器返回一个迭代器,调用next()方法会执行代码直到遇到下一个yield语句。

使用Generator实现异步流程控制

Generator可以用来处理异步操作,但通常需要一个运行器函数:


function fetchData(url) {return new Promise((resolve, reject) => {setTimeout(() => {if (Math.random() > 0.3) {resolve(`来自${url}的数据`);} else {reject(`获取${url}失败`);}}, 1000);});}function* fetchSequence() {try {const user = yield fetchData('/api/user');console.log(user);const posts = yield fetchData('/api/posts');console.log(posts);const comments = yield fetchData('/api/comments');console.log(comments);return '所有数据获取完成';} catch (error) {console.error('出错了:', error);return '数据获取过程出错';}}// 手动运行生成器function runGenerator(generatorFn) {const generator = generatorFn();function handle(result) {if (result.done) return Promise.resolve(result.value);return Promise.resolve(result.value).then(res => handle(generator.next(res))).catch(err => handle(generator.throw(err)));}return handle(generator.next());}runGenerator(fetchSequence).then(result => console.log(result)).catch(err => console.error(err));

4、async/await语法

尽管Promise已经比回调函数有了很大改进,但ES2017引入的async/await语法进一步简化了异步编程,使异步代码看起来更像同步代码。

async/await基础

  • async:声明一个异步函数,它会返回一个Promise

  • await:暂停异步函数的执行,等待Promise解决

实现原理

(1)生成器与迭代器

async/await 的核心原理是利用生成器函数(Generator)的暂停和恢复能力:

function* genFunc() {yield 1;yield 2;
}

生成器可以通过 yield 暂停执行,并在之后通过 next() 恢复执行。

(2)Promise 结合

async/await 将 Generator 与 Promise 结合:

  • async 标记的函数总是返回 Promise
  • await 操作会暂停函数执行,等待 Promise 完成

(3)自动执行器

关键环节是一个自动执行器,负责:

  1. 执行生成器函数
  2. 当遇到 yield 时暂停
  3. 等待 Promise 解决
  4. 将结果传回生成器并恢复执行

简化版实现

一个简化的 async/await 实现可以是:

function asyncToGenerator(generatorFunc) {return function() {const gen = generatorFunc.apply(this, arguments);return new Promise((resolve, reject) => {function step(key, arg) {let result;try {result = gen[key](arg);} catch (error) {return reject(error);}const { value, done } = result;if (done) {return resolve(value);} else {return Promise.resolve(value).then(val => step("next", val),err => step("throw", err));}}step("next");});};
}

Babel 转译示例

以下是 Babel 如何将 async/await 转译为 ES5 代码(简化版):

// 原始 async 函数
async function foo() {const result = await someAsyncFunc();return result;
}// 转译后
function foo() {return _asyncToGenerator(function* () {const result = yield someAsyncFunc();return result;});
}

工作流程

  1. 当调用 async 函数时,自动创建一个 Promise 对象
  2. 函数体内代码正常执行,直到遇到 await 表达式
  3. await 表达式会暂停当前函数执行
  4. await 后的表达式会被转换成 Promise(如果不是已经是 Promise)
  5. 当该 Promise 完成时,恢复函数执行并返回 Promise 的结果
  6. 如果 Promise 被拒绝,await 表达式会抛出错误

// 使用async/await重写前面的例子async function getUserInfo(userId) {try {const user = await fetchUserData(userId);console.log("用户数据:", user);const posts = await fetchUserPosts(user.id);console.log("用户文章:", posts);const comments = await fetchPostComments(posts[0].id);console.log("文章评论:", comments);const author = await fetchCommentAuthor(comments[0].authorId);console.log("评论作者:", author);return author;} catch (error) {console.error("发生错误:", error);}}// 调用异步函数getUserInfo(123).then(result => console.log("最终结果:", result));

相比Promise链,async/await的优势:

  • 代码结构清晰,接近同步写法

  • 便于使用条件语句和循环

  • 易于进行错误处理

  • 调试更简单

并行执行

虽然await会暂停函数执行,但有时我们需要并行执行多个异步操作:


async function fetchAllData() {// 错误示范:串行执行,效率低const users = await fetchUsers();const posts = await fetchPosts();const comments = await fetchComments();// 正确示范:并行执行const [users, posts, comments] = await Promise.all([fetchUsers(),fetchPosts(),fetchComments()]);return { users, posts, comments };}

错误处理

async函数中可以使用try/catch来捕获错误,也能捕获await的Promise拒绝:


async function fetchWithErrorHandling() {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error(`HTTP错误: ${response.status}`);}const data = await response.json();return data;} catch (error) {console.error("获取数据失败:", error);// 可以返回默认值return { error: true, message: error.message };}}

Generator vs async/await

在ES2017引入async/await之前,Generator曾经是实现异步控制流的重要工具。现在,async/await基本上取代了Generator在异步编程中的角色,因为:

  • async/await是基于Generator和Promise的语法糖,更易于使用

  • async函数无需运行器,浏览器原生支持

  • 错误处理更加直观

然而,Generator在某些场景(如惰性计算、状态机实现)中仍然非常有用。

5、事件循环机制

要真正理解JavaScript的异步编程,必须了解底层的事件循环机制。JavaScript是单线程的,依靠事件循环来处理异步操作。

事件循环的关键组件

事件循环机制涉及以下几个关键组件:

  • 执行栈(Call Stack):管理函数调用的栈结构,遵循"后进先出"原则

  • 宏任务队列(Macrotask Queue):存放宏任务,如setTimeout、setInterval、I/O等

  • 微任务队列(Microtask Queue):存放微任务,如Promise回调、MutationObserver等

  • 事件循环(Event Loop):持续检查执行栈和任务队列的循环过程

宏任务与微任务

宏任务(Macrotask)包括:

  • script(整体代码)

  • setTimeout/setInterval

  • setImmediate(Node.js环境)

  • I/O操作

  • UI渲染(浏览器)

  • requestAnimationFrame(浏览器)

微任务(Microtask)包括:

  • Promise.then/catch/finally

  • MutationObserver

  • process.nextTick(Node.js环境)

  • queueMicrotask()

事件循环的基本流程

(1) 开始:执行第一个宏任务,即全局代码(script)

(2) 同步代码执行

  • 所有同步代码进入执行栈按顺序执行

  • 如遇异步API,其回调函数被分发到对应的任务队列中

(3) 执行栈清空

  • 同步代码执行完毕,执行栈清空

(4) 处理微任务

  • 检查微任务队列,有微任务则依次执行所有微任务

  • 执行过程中产生的新微任务也会在当前循环中执行

(5) UI渲染(仅浏览器环境):

  • 如有必要,进行页面渲染更新

(6) 处理宏任务

  • 从宏任务队列取出一个任务执行

  • 执行完后,返回步骤3,检查微任务队列

(7) 循环往复

  • 事件循环无限继续,直到所有任务队列清空

事件循环流程图:

在这里插入图片描述

实际例子解析


console.log('1. 开始'); // 同步代码setTimeout(() => {console.log('2. 第一个宏任务');Promise.resolve().then(() => {console.log('3. 宏任务中的微任务');});}, 0);Promise.resolve().then(() => {console.log('4. 第一个微任务');setTimeout(() => {console.log('5. 微任务中的宏任务');}, 0);});console.log('6. 结束'); // 同步代码// 输出顺序: 1 -> 6 -> 4 -> 2 -> 3 -> 5

(1) 第一个宏任务(script全局代码)

  • 执行同步代码,打印"1. 开始"

  • 遇到setTimeout,其回调被添加到宏任务队列

  • 遇到Promise.then,其回调被添加到微任务队列

  • 执行同步代码,打印"6. 结束"

  • 同步代码执行完毕,执行栈清空

(2) 检查微任务队列

  • 执行微任务,打印"4. 第一个微任务"

  • 遇到setTimeout,其回调被添加到宏任务队列

  • 微任务队列清空

(3) 进行UI渲染(如需)

(4) 取出下一个宏任务

  • 执行第一个setTimeout的回调,打印"2. 第一个宏任务"

  • 遇到Promise.then,其回调被添加到微任务队列

(5) 再次检查微任务队列

  • 执行微任务,打印"3. 宏任务中的微任务"

  • 微任务队列清空

(6) 进行UI渲染(如需)

(7) 取出下一个宏任务

  • 执行第二个setTimeout的回调,打印"5. 微任务中的宏任务"

关于async/await在事件循环中的位置

前面讲到async/await 就是生成器和Promise的语法糖,它的工作流程中讲到,await 表达式会暂停当前函数执行,await 后的表达式会被转换成 Promise(如果不是已经是 Promise),所以:

  • 当函数遇到 await 时,会将后续代码作为微任务放入事件循环
  • 这就是为什么 await 之后的代码总是在当前同步代码执行完毕后执行

6、异步编程最佳实践

使用Promise而非回调

所有新代码应该优先使用Promise API而非传统回调:


// 不推荐function fetchData(callback) {setTimeout(() => {callback(null, { data: 'success' });}, 1000);}// 推荐function fetchData() {return new Promise((resolve) => {setTimeout(() => {resolve({ data: 'success' });}, 1000);});}

优先使用async/await

对于大多数异步操作,使用async/await可以使代码更清晰:


// Promise链function getUserData(userId) {return fetchUser(userId).then(user => {return fetchPosts(user.id).then(posts => {user.posts = posts;return user;});});}// 使用async/awaitasync function getUserData(userId) {const user = await fetchUser(userId);user.posts = await fetchPosts(user.id);return user;}

正确处理错误

异步代码中的错误处理尤为重要:


// Promise错误处理fetchData().then(data => processData(data)).then(result => displayResult(result)).catch(error => {console.error('发生错误:', error);showErrorMessage(error);});// async/await错误处理async function handleData() {try {const data = await fetchData();const result = await processData(data);displayResult(result);} catch (error) {console.error('发生错误:', error);showErrorMessage(error);}}

避免嵌套async函数

当不需要等待内部异步操作时,避免嵌套async函数:


// 不好的实践async function processItems(items) {const results = [];for (const item of items) {// 没必要使用async函数results.push(await (async () => {const data = await fetchData(item.id);return processData(data);})());}return results;}// 更好的实践async function processItems(items) {const results = [];for (const item of items) {const data = await fetchData(item.id);results.push(processData(data));}return results;}

合理使用Promise并行执行

当多个异步操作相互独立时,应该并行执行它们:


// 低效方式:串行执行async function loadData() {const users = await fetchUsers();const products = await fetchProducts();const categories = await fetchCategories();return { users, products, categories };}// 高效方式:并行执行async function loadData() {const [users, products, categories] = await Promise.all([fetchUsers(),fetchProducts(),fetchCategories()]);return { users, products, categories };}

避免不必要的async/await

不是所有返回Promise的函数都需要async关键字:


// 不必要的asyncasync function getData() {return fetch('/api/data').then(r => r.json());}// 简化版本function getData() {return fetch('/api/data').then(r => r.json());}

使用Promise工具方法

利用Promise提供的静态方法简化常见任务:


// 并行请求并使用所有结果Promise.all([fetchUsers(), fetchPosts(), fetchComments()]).then(([users, posts, comments]) => {// 处理所有数据});// 超时处理function fetchWithTimeout(url, timeout = 5000) {return Promise.race([fetch(url),new Promise((_, reject) => {setTimeout(() => reject(new Error('请求超时')), timeout);})]);}// 任一请求成功即可function fetchFromMultipleSources(urls) {return Promise.any(urls.map(url => fetch(url)));}

编写可测试的异步代码

良好的异步代码应该易于测试:


// 可测试的异步函数async function processUserData(userId) {const user = await fetchUser(userId);if (!user) {throw new Error('用户不存在');}user.lastActive = new Date();return saveUser(user);}// 测试代码test('processUserData成功处理用户', async () => {// 使用mock替换真实APIfetchUser = jest.fn().mockResolvedValue({ id: 1, name: '张三' });saveUser = jest.fn().mockResolvedValue({ success: true });const result = await processUserData(1);expect(result.success).toBe(true);expect(saveUser).toHaveBeenCalledWith(expect.objectContaining({id: 1,lastActive: expect.any(Date)}));});

总结

JavaScript异步编程经历了从回调函数、Promise、Generator到async/await的演进。这些技术的发展使得异步代码越来越接近同步代码的直观性和可维护性,同时保留了非阻塞执行的优势。

理解事件循环机制是掌握JavaScript异步编程的关键,它解释了不同类型任务的执行顺序。在实际开发中,合理选择异步编程技术、遵循最佳实践,可以帮助我们编写出高效、可靠和易于维护的异步代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/72136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kali liux的下载

Kali Linux | Penetration Testing and Ethical Hacking Linux Distributionhttps://www.kali.org/ VMware虚拟机https://pan.quark.cn/s/aa869ffbf184 【补充一个今天学到的知识昂和内容无关:(遥感)指非接触的远距离探测技术,使用传感器探…

windows下玩转vllm:在wsl下安装vllm

文章目录 前言安装wsl启动wsl的默认分发使用python部署vllm创建并激活虚拟环境直接说结论试错过程安装vllm简单测试一下,看看行不行附录,安装wsl安装ubuntu分发步骤 3: 设置用户和密码步骤 4: 更新系统步骤 5: 使用 WSL前言 当前,部署通义千问2.5-vl已经是一件箭在弦上,不…

《信息论与编码》课程笔记——绪论和离散信源(一)

目录 绪论 一、信息论的基本概念 1.1 信息的定义 1.2 信息的三个层次 二、香农信息论的研究内容 2.1 信源与信源编码 2.2 信道与信道编码 2.3 保密通信与密码学 离散信源(一) 一、自信息 1.1 定义 二、离散信源 2.1 定义 2.2 符号表示 三、…

大模型中的Token到底是什么?

文章目录 引言什么是Token?定义举例说明中文Tokenization Tokenization的复杂性子词TokenizationBPE算法BPE示例 可视化BPE过程 Token在大模型中的作用输入表示上下文理解输出生成 Tokenization的挑战语言差异未登录词计算效率 Token消耗的规则Token消耗的基本规则示…

Acwing 哞叫时间II

6134. 哞叫时间II - AcWing题库 题目大意:统计数组中子序列abb的数量: 做法:从右往左枚举倒数第二个b,查前面出现过多少次a,查的方法(开一个数组left[x]来统计当前及前面出现过多少次x,cnt记录不同x的数量…

Go中slice和map引用传递误区

背景 关于slice和map是指传递还是引用传递,很多文章都分析得模棱两可,其实在Go中只有值传递,但是很多情况下是因为分不清slice和map的底层实现,所以导致很多人在这一块产生疑惑,下面通过代码案例分析slice和map到底是…

20250225-代码笔记03-class CVRPModel AND other class

文章目录 前言一、class CVRPModel(nn.Module):__init__(self, **model_params)函数功能函数代码 二、class CVRPModel(nn.Module):pre_forward(self, reset_state)函数功能函数代码 三、class CVRPModel(nn.Module):forward(self, state)函数功能函数代码 四、def _get_encodi…

使用Hydra进行AI项目的动态配置管理

引言:机器学习中的超参数调优挑战 在机器学习领域,超参数调优是决定模型性能的关键环节。不同的模型架构,如神经网络中的层数、节点数,决策树中的最大深度、最小样本分割数等;以及各种训练相关的超参数,像学习率、优化器类型、批量大小等,其取值的选择对最终模型的效果…

preg_replace 与 str_replace 的比较与选择

preg_replace 与 str_replace 的比较与选择 ——PHP字符串处理的核心工具深度解析 一、核心功能定位 在PHP的字符串处理中,str_replace和preg_replace是两种最常用的替换函数,但其设计目标和应用场景存在本质差异: str_replace 简单字符串替…

嵌入式开发:傅里叶变换(4):在 STM32上面实现FFT(基于STM32L071KZT6 HAL库+DSP库)

目录 步骤 1:准备工作 步骤 2:创建 Keil 项目,并配置工程 步骤 3:在MDK工程上添加 CMSIS-DSP 库 步骤 5:编写代码 步骤 6:配置时钟和优化 步骤 7:调试与验证 步骤 8:优化和调…

【MySQL篇】数据类型

目录 前言: 1,数据类型的分类 ​编辑 2 ,数值类型 2.1 tinyint类型 2.2 bit类型 2.3 小数类型 2.3.1 float类型 2.3.2 decimal类型 3,字符串类型 3.1 char 3.2 varchar 3.3 char与varchar的比较 3.4日期和时间类型 3.5 …

nuxt常用组件库html-validator应用解析

html-validator 主要用于自动验证nuxt服务器呈现的HTML(SSR和SSG),以检测可能导致水合错误的HTML常见问题,有助于减少水合错误,检测常见的可访问性错误。 安装 npx nuxilatest module add html-validator配置 若自动更新nuxt.config.ts配置文…

智能图像处理平台:图片管理

接着我们讲图片管理,先实现图片基础的增删改查,再去考虑图像处理。 主要是,我们需要完成查询时,查询的图片的上传者的角色等级小于等于我们当前登陆账号。 后端controller: package com.llpp.controller;import cn.…

大模型知识蒸馏技术(8)——知识蒸馏应用场景

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl1. 知识蒸馏概述 知识蒸馏是一种将大型复杂模型(教师模型)的知识迁移到小型简单模型(学生模型)的技术。其核心原理是通过教师模型的输出(通常是softmax后的概率分布)来指导学生模型的训练,…

LeetCode:131. 分割回文串(DP Java)

目录 131. 分割回文串 题目描述: 实现代码与解析: 动态规划 原理思路: 131. 分割回文串 题目描述: 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。…

INT202 Complexity of Algroithms 算法的复杂度

文章目录 1. 前言1.1 算法(Algorithms)和数据结构(Data Structure)1.2 什么是好的算法?1.3 算法分析1.3.1 实验分析(Experimental Analysis)1.3.2 理论分析1.3.2.1 伪代码(Pseudo-co…

BDF报告翻译简介后:关于A φ方法criterion引理1如何由范数导出内积

关于A φ方法criterion 引理1 如何由范数导出内积 在数学中,特别是在泛函分析中,给定一个范数,可以定义一个与之相关的内积。这个过程不是总是可能的,但当一个赋范向量空间是完备的且满足平行四边形恒等式时,可以导出…

初识uniApp

详细思考一下uniApp这个跨平台开发框架。首先,我对uniApp还不是很了解,所以需要从基本概念开始,逐步深入。 什么是uniApp? 我记得uniApp是基于Vue.js的,可能是一个用来开发多个平台的应用的框架。用户可能想了解它是什…

olmOCR:使用VLM解析PDF

在PDF解析中,目前主流的开源工具包括Minuer、GOT OCR等。主要都是通过飞桨等OCR套件组装的一套pipeline,或者直接通过VLM解析图像。 #一、 olmOCR是使用VLM进行的端到端的PDF文档解析 二、document-anchoring 与上述的不同在于,olmOCR使用…

Nginx 代理配置导致浏览器应用网页页面加载失败的分析与解决

Nginx 代理配置导致应用页面加载失败的分析与解决 前期部署信息: 部署DM数据库DEM时,配置了nginx代理,conf配置内容如下: charset utf-8;client_max_body_size 128M;listen 4567;server_name 192.168.1.156;root /opt/h5/;index…