js中有两种实现异步的方式
传统的回调函数
比如使用setTimeout让一个函数在指定的时间后执行 ,这个函数本身会立刻返回,程序紧接着执行之后的代码,但是自己传入的回调函数则会等到预定的时间才会执行
setTimeout(()=>{
console.log("兄弟你好");
},3000);
console.log("你会立刻看到我");
Javascript从设计之初就是一个单线程的编程语言,即便看上去回调函数和主程序在并发执行,但是它们都运行在同一个主线程当中
单线程的优点:无需考虑线程同步或者是资源竞争的问题,从源头上避免了线程之间的频繁切换,降低了线程自身的开销
缺点:
回调函数虽然简单好理解,但是有一个明显的缺点,如果需要依次执行多个异步操作,程序可能会写成如下这样,当地一个任务执行完成之后,在回调函数中再去执行第二个任务,之后是第三个,第四个,整个程序会一层一层的嵌套下去,可读性变差,也就是函数的回调地狱 。
setTimeout(){
console.log("等三秒后");
setTimeout(){
console.log("再等三秒后");
setTimeout(()=>{
console.log("又等三秒");
//...
},3000);
},3000);
},3000);
因此,Promise应运而生
javaScript使用promise当中的API ,fetch(’http://...‘)就是一个很好的例子,用来发起一个请求来获取服务器数据,可以用它来动态更新页面的内容,也就是平时说的Ajax技术
我们随后可以调用它的then方法并传递一个回调函数,如果请求在未来成功完成,那么回调函数会被调起,请求的结果也会用参数的形式传递进来
fetch("https://jsonplaceholder.typicode.com/post/1")
.then(()=>{})
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response)=>{
//......
})
Promise的优点:可以用一种链式结构将多个异步操作串联起来
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response)=>response.json());
这里面的response.json()方法也会返回一个Promise,代表
未来的某个时刻将返回的数据转换成JSON格式,如果想要等到他完成
之后再执行其它操作,可以在后面追加一个.then
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response)=>response.json());
.then((json)=>console.log(json));
Promise的链式调用,避免了代码的层层嵌套
对比:
代码是向下方增长而不是向右,因此可读性会提升很多
//回调函数
setTimeout(){
console.log("等三秒后");
setTimeout(){
console.log("再等三秒后");
setTimeout(()=>{
console.log("又等三秒");
//...
},3000);
},3000);
},3000);
//链式调用:
fetch("https://...")
.then(...)
.then(...)
.then(...)
.then(...)
.then(...);
捕捉错误:
同时Promise提供finally方法,在Promise链结束之后调用,无论失败与否,可以在这里做一些清理工作
fetch("https://jsonplaceholder.typicode.com/post/1")
.then((response)=>response.json());
.then({(json)=>console.log(json);})
.catch((error)=>{
console.log(error);
})
.finally(())=>{
//执行清理操作等等
//清理加载动画 :
stopLoadingAnimation();
})
async和await是基于Promise之上的一个语法糖,可以让异步操作更加的简洁明了
第一:使用async关键字将函数标记为异步函数,也就是返回值是Promise对象的函数。
比如之前用到的fetch()就是一个异步函数,在异步函数中可以调用其它的异步函数,不过,不再需要使用then,而是一个更加简洁的await语法
await会等待Promise完成之后直接返回最终的结果。所以
async function f(){
await fetch("http://...");
const response=await fetch("http://...");
//这里的response已经是服务器返回的响应数据了
const json=await response.json();
console.log(json);
}
f();//注意:这个函数返回值永远是Promise对象
await虽然看上去会暂停函数的执行,但是在等待的过程中,javaScript同时可以处理其它的任务,比如更新界面,运行其他的程序代码等等,这是因为await底层是基于Promise和事件循环机制实现的
async funtion f(){
const a=await fetch("http://.../post/1");
const b=await fetch("http://.../post/2");
//...
Promise.all([promiseA,promiseB]);
//...
const [a,b]=await Promise.all([promiseA,promiseB]);
}
第二个:如果我们需要在循环中执行异步操作,不能直接调用forEach或者是map这一类方法的。
尽管在回调函数中写了await,但是里面的forEach会立刻返回,并不会暂停等到所有的异步操作都执行完毕,
如果希望等待循环中的异步操作都一一完成之后才继续执行,还是应该使用传统的for循环
async function f(){
[1,2,3].forEach(async(i)=>{
await someAsyncOperation();
});
console.log("done");
//使用for循环
for(let i of [1,2,3]){
await someAsyncOperation();
}
console.log("done");
}
f();
更进一步,如果想要循环中的所有操作都并发执行,更炫酷的写法是使用for await,这里的for循环依然会等到所有的异步操作都完成之后才继续向后执行
async funtion f(){
const promise=[
someAsyncOperation(),
someAsyncOperation(),
someAsyncOperation(),
];
for await(let result of promises){
//...
}
console.log("done")
}
f();
第三个:我们不能在全局或者普通函数中直接使用await关键字,await只能被用在异步函数(async funtion)中
await someAsyncOperation(); //❌
如果想要在最外层中使用await,那么需要先定义一个异步函数,然后在函数体当中使用它
async funtion f(){
await someAsyncOperation();
}
f();
//或者是更加简洁的写法
(async()=>{
await someAsyncOperation();
})();
使用async和await之后,可以写出更加清晰地异步代码,有了它们之后,我们几乎不再需要使用底层的Promsie对象,包括调用它们的then(),catch()函数等等。
拓展:
一:.Promise.all()
方法的作用是什么
Promise.all()
方法同时处理多个Promise。它以一个Promise数组作为输入,并且返回一个新的Promise。这个新的Promise在输入数组中的所有Promise都已经解决的时候才会解决。已经解析的Promise的值在.then()
块中作为数组按照输入Promise的顺序可用。
2.如何处理 Promise 中的错误?
可以使用 try { } catch(err) { } finally 块来处理 Promise 链中的错误,
3.同步操作和异步操作之间有什么区别?
同步操作会阻止程序的执行,直到操作完成。它们以顺序方式一个接一个地执行。而异步操作不会阻止程序的执行,它们允许程序在等待操作完成时继续执行其他任务。异步操作通常用于可能需要较长时间才能完成的任务,例如网络请求或文件操作,以避免阻塞主执行线程
4. JavaScript 中回调函数和 Promise 之间有什么区别?什么时候更喜欢使用 Promise而不是回调?另外,回调地狱是什么,如何缓解它?
回调是 JavaScript 中处理异步操作的传统方式,它们是作为参数传递给其他函数的函数,并在异步操作完成时调用。Promise 是表示异步操作最终完成(或失败)的对象。与回调相比,Promise 提供了更结构化和可读的代码。通常,当处理复杂的异步操作、错误处理和代码可读性时,更倾向于使用 Promise 而不是回调。回调地狱是指代码结构变得嵌套层次很深,每个回调都作为另一个回调的参数传递。这种嵌套会很快变得复杂,使代码难以理解,导致问题,如代码重复、错误处理问题以及难以维护和调试的困难。为了缓解回调地狱,可以使用几种方法,例如使用命名函数、使用控制流库(如 asyncjs 或 Promises)或使用现代 JavaScript 特性如 async/await。这些方法有助于扁平化代码结构,使其更可读和可维护,避免过多的回调嵌套。
5.用Promise解释.catch() 方法的用途:
它与.then()方法有什么不同?promises 中的.catch()方法用于错误处理。它允许您指定一个回调函数,当promise 在执行过程中被拒绝或遇到错误时将调用该回调函数。.then()用于处理完成的 promises 和成功的结果。
6.Promise.resolve和Promise.reject()的区别是什么?
Promise.resolve:它返回用给定值解析的 promises。如果提供的值已经是 Promise,则按原样返回。如果该值不是 Promise, Promise.Resolve()将创建一个新的Promise,该新 Promise 将立即使用提供的值进行解析。Promise.reject0):它返回因给定原因或错误而被拒绝的 promises。所提供的原因或错误被视为拒绝 promises 的原因。
儿童节快乐,希望,世界和平。