晚上睡不着觉,浅浅的看了几篇文章,梳理一下自己对同步异步问题的理解
问题1 为什么在开发过程中更推荐异步编程呢(如 async/await)?
下面描述一下异步编程的好处:
非阻塞性,使用async/await使代码在等待网络请求的响应过程中不会阻塞主进程,这里要解释一下,因为js是单线程的,意味着它只能同时执行一个任务,如果使用同步方式调用接口,代码会在请求完成之前完全停止执行,可能会导致页面”卡死“,用户可能会觉得应用无响应,体验感会不好,而使用异步编程可以让程序在等待接口响应导时继续执行其他操作,比如更新页面、点击按钮等其他操作。
错误处理
异步:在async/await函数中,可以使用try/catch语句来捕获异常中的错误.
同步:在同步代码中,错误处理可能会更复杂,尤其是在涉及多个异步操作时.
异步编程中async/await可以使代码更接近于同步代码结构,易于理解和维护,可以像写同步代码一样按照逻辑顺序书写异步代码,同时也避免了回调地狱的问题
**注:他俩最大的区别:异步请求允许多个请求同时进行,而不需要等待每个请求完成后再开始下一个,这种并发处理可以提高性能,尤其在需要同时在多个接口获取数据时。
问题2 异步编程和同步编程的使用场景分别是什么?
同步编程:小文件的读写、程序启动时的初始化操作、以及最常见的例子吧,绿-黄-红 灯的执行顺序
异步编程: 大文件的读写、网络请求、并发任务、定时任务
问题3 await可以单独使用吗?
答案是:当然不可以了,await必须和async配套使用,如果一个函数使用了async,他会返回一个promise,而await的作用就是暂停当前的async函数的执行,直到promise完成,如果单独使用await一定会报错,因为js引擎无法提供必要的异步上下文,导致语法错误,下面贴点代码举个例子:
const deleteTemplate = async (id: string) => {
console.log("1111111");
const result = await removeTemplate(id)
console.log("2222222");
if (result?.success) {
message.success({
content: "模板删除成功",
class: MESSAGE_CLS.SUCCESS,
});
await refreshList();
} else if (result?.message == "模板已被使用,不能删除") {
message.warn({
content: "模板已被使用,不能删除",
class: MESSAGE_CLS.WARN,
});
await refreshList();
} else {
message.warn({
content: result.message,
class: MESSAGE_CLS.WARN,
});
}
console.log("333333");
}
};
我之前一直有个疑问,都有await了,还要等待接口响应成功才执行后续的操作,那为啥不直接用同步编程呢,现在看到这些解释,嗯,可以说的通了哈哈,那考一下,这段代码中的执行顺序是什么?
1111111
2222222
333333
问题4 什么是Promise?
Promise是js中用于处理异步操作的一种机制,它代表一个可能在未来某个时间点完成的操作的结果。Promise可以处于三种状态之一:
Pending(待定):初始状态,既不是成功,也不是失败。
Fulfilled(已完成):操作成功完成,Promise也会变为成功状态。
Rejected(已拒绝):操作失败,Promise变为失败状态
Promise的基本用法
创建 Promise
可以使用Promise构造函数来创建一个新的Promise。构造函数接受一个执行器函数,该函数有两个参数:resolve和reject.
const myPromise = new Promise((resolve, reject) => {
// 异步操作
const success = true;
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
});
使用Promise
// 第一种通过async/await(推荐!!!)
const handlePromise = async () => {
try {
// 等待 Promise 解析
const result = await myPromise;
// 处理成功的情况
console.log("成功:", result);
} catch (e) {
// 处理失败的情况
console.error("失败:", error)
}
}
handlePromise(); // 调用 async 函数
// 第二种 使用 then 和 catch 方法来处理 Promise 的结果
const handlePromise = () => {
myPromise.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
})
}
handlePromise(); // 调用 函数
Promise.all的用法
Promise.all:接受一个Promise数组,只有当所有的Promise都成功时,才会返回一个成功的Promise。如果有任何一个Promise失败,则会返回失败的Promise
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
const urls = ['url1', 'url2', 'url3'];
Promise.all(urls.map(fetchData)).then((results) => {
console.log(results); // ['Data from url1', 'Data from url2', 'Data from url3']
}).catch((error) => {
console.error('Error fetching data:', error);
});
回调地狱的问题
回调地狱(Callback Hell)是指在js 中使用回调函数处理异步操作时,代码层级嵌套过深,导致代码可读性差、维护困难的现象,通常发生在需要进行多个异步操作时,每个操作的结果都依赖于前一个操作的完成。
简单举个回调地狱的示例,假设我们需要依次执行三个异步操作,每个操作的结果都依赖于前一个操作
function asyncOperation1(callback) {
setTimeout(() => {
console.log("操作1完成");
callback("结果1");
}, 1000);
}
function asyncOperation2(resultFromOp1, callback) {
setTimeout(() => {
console.log("操作2完成,接收到:", resultFromOp1);
callback("结果2");
}, 1000);
}
function asyncOperation3(resultFromOp2, callback) {
setTimeout(() => {
console.log("操作3完成,接收到:", resultFromOp2);
callback("结果3");
}, 1000);
}
// 使用回调函数进行嵌套
asyncOperation1(result1 => {
asyncOperation2(result1, result2 => {
asyncOperation3(result2, result3 => {
console.log("所有操作完成,最终结果:", result3);
});
});
});
在说一下解决方案
先说最推荐的,使用 async/await,使异步代码看起来更像同步代码,进一步提高了可读性
async function executeOperations() {
try {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2(result1);
const result3 = await asyncOperation3(result2);
console.log("所有操作完成,最终结果:", result3);
} catch (error) {
console.error("发生错误:", error);
}
}
executeOperations();
使用 Promise:Promise 可以将异步操作的结果封装起来,避免深层嵌套。
function asyncOperation1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("操作1完成");
resolve("结果1");
}, 1000);
});
}
function asyncOperation2(resultFromOp1) {
return new Promise(resolve => {
setTimeout(() => {
console.log("操作2完成,接收到:", resultFromOp1);
resolve("结果2");
}, 1000);
});
}
function asyncOperation3(resultFromOp2) {
return new Promise(resolve => {
setTimeout(() => {
console.log("操作3完成,接收到:", resultFromOp2);
resolve("结果3");
}, 1000);
});
}
// 使用 Promise 链式调用
asyncOperation1()
.then(result1 => asyncOperation2(result1))
.then(result2 => asyncOperation3(result2))
.then(result3 => {
console.log("所有操作完成,最终结果:", result3);
})
.catch(error => {
console.error("发生错误:", error);
});
讲个在开发过程中遇到的问题
根据模板生成正式报告:见名知意,意识就是根据文件模板生成一份正式报告,也就是说要先把模板下载到本地,然后在本地进行占位符替换,生成新文件,再把新文件上传。看一段代码:
const generateReportDraft = async (
templateInfo: {
id: string;
name: string;
templateType: number;
type: string;
// 新
fileId: string;
path: string;
},
replaceForTempTemplate: ReplaceData["replaceForTempTemplate"],
projectId: string,
projectSubitemId: string, // 子项目的 id
sysCompanyCode: string | number,
dataDiskBasePath: string,
parentFolder: {
fileId: string;
relativePath: string;
absolutePath: string;
} // 初稿(临时模板)保存地址
): Promise<{
success: boolean;
localDir: string;
message?: string;
reportRecord?: Partial
editorConfig?: any;
docService?: string;
}> => {
// debugger;
let localDir = "";
if (
!templateInfo.id ||
!projectId ||
!replaceForTempTemplate ||
!projectSubitemId ||
!parentFolder
) {
failRes.message = "缺少参数";
return failRes;
}
const {
id: templateId,
name: templateName,
type: templateFileExt,
path: templatePath,
} = templateInfo;
const templateRemotePath = dataDiskBasePath + templatePath;
const remotePath = parentFolder.absolutePath + "/" + templateName;
// 下载临时模版
const downloadRes = await downloadTemplateFileByApi(
{
name: templateName,
path: templateRemotePath,
templateId: templateId,
ext: templateFileExt
},
TransferChannel.DT
);
const templateLocalPath = downloadRes.result?.localPath || "";
localDir = downloadRes.result?.localDir || "";
failRes.localDir = localDir;
if (!downloadRes?.success || !templateLocalPath || !localDir) {
failRes.message = downloadRes.message || "下载模板失败";
return failRes;
}
// 循环最多尝试10次,检查模板文件是否存在 如果文件不存在,等待100毫秒后再次检查
// 因为接口下载文件 需要时间,会有文件不存在的情况
for (let i = 0; i < 10; i++) {
const {owner} = await checkExist("local", templateLocalPath);
if (owner && owner !== void 0) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100)); // 等待1秒
}
// 在本地下载的模版进行占位符替换
下面代码省略
};
其中的关键就在:
// 循环最多尝试10次,检查模板文件是否存在 如果文件不存在,等待100毫秒后再次检查
// 因为接口下载文件 需要时间,会有文件不存在的情况
for (let i = 0; i < 10; i++) {
const {owner} = await checkExist("local", templateLocalPath);
if (owner && owner !== void 0) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100)); // 等待1秒
}
因为是项目改造,之前下载文件用的是sftp直连下载,下载速度很快,并没有发现在本地进行替换的时候文件不存在的问题,但是这次改成接口下载之后,下载速度慢相对慢一些,就发现了问题所在,会有文件不存在的情况,当时也挺搞笑的,怎么也想不到因为什么,然后打断点因为会有时间差,在这个时间差内文件已经下载完了,所以打断点的时候一切正常,还看不出什么,后来请教前辈,前辈提点了一下,恍然大悟哈哈
好啦,就写到这里吧,写文章真的好费时间,只是个人理解,仅供参考哦