js-同步与异步
@
① 同步API, 异步API
同步API:同步任务都在主线程上执行,形成一个执行栈。 只有当前API执行完成后,才能继续执行下一个API
console.log('before');
console.log('after');
/*输出结果
*、
异步API:当前API的执行不会阻塞后续代码的执行
JS 的异步是通过回调函数实现的。
回调函数: 自己定义函数让别人去调用
// getData函数定义
function getData (callback) {}
// getData函数调用
getData (() => {});
一般而言,异步任务有以下三种类型:
1、事件函数, 如 click、 resize
等
2、资源加载, 如load、 error
等
3、定时器, 包括 setInterval、 setTimeout
等
4、ajax
异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)
console.log('before');
//异步API不会阻塞后续代码的执行
setTimeout(() => {console.log('last');}, 2000);
console.log('after');
/*
输出结果:
before
after
last
*/
② js执行机制
JavaScript的执行环境是「单线程」
所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
异步模式可以一起执行多个任务
- 先执行执行栈中的同步任务。
- 异步任务(回调函数)放入任务队列中。
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈, 开始执行。
由于主线程不断的重复获得任务、执行任务、 再获取任务、 再执行, 所以这种机制被称为事件循环( event loop) 。
console.log(1);
document.onclick = function() {
console.log('click');
}
console.log(2);
setTimeout(function() {
console.log(3)
}, 3000)
控制台输出结果
1
2
//点击屏幕之后
click
//3秒之后
3
//点击屏幕之后
click
由于这种机制,使得异步与同步一起时会出现一些问题,比如:
- 同步API不可直接从返回值中拿到异步API执行的结果
// 异步 function getMsg(callback) { setTimeout(function() { callback({ msg: 'hello node.js' }) }, 2000) } let msg = null; getMsg(function(data) { //console.log(data); msg = data; console.log(msg); }); console.log(msg); /*执行输出 null { msg: 'hello node.js' } */
- 同步API从上到下依次执行,前面代码会阻塞后面代码的执行,异步API不会等待API执行完成后再向下执行代码,使用回调函数获取异步API执行结果
console.log('代码开始执行'); setTimeout(() => { console.log('2秒后执行的代码'); }, 2000); setTimeout(() => { console.log('"0秒"后执行的代码'); }, 0); console.log('代码结束执行');
③ 回调地狱
有时为了保证执行顺序,不得不在异步操作的回调中继续异步操作,如此不断嵌套,便形成 回调地狱,比如:
- ajax的异步请求
$.ajax({
url: 'http://localhost:3000/data',
success: function(data) {
console.log(data)
$.ajax({
url: 'http://localhost:3000/data1',
success: function(data) {
console.log(data)
$.ajax({
url: 'http://localhost:3000/data2',
success: function(data) {
console.log(data)
}
});
}
});
}
});
- nodeJs中的文件读取
问题:如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
// 文件读取为异步操作
fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');
需求:依次读取A文件、B文件、C文件
const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, result1) => {
console.log(result1)
fs.readFile('./2.txt', 'utf8', (err, result2) => {
console.log(result2)
fs.readFile('./3.txt', 'utf8', (err, result3) => {
console.log(result3)
})
})
});
回调地狱: 回调嵌套回调,虽然可实现上述需求,可代码可读性差,难以维护
④ Promise (ES6)
Promise 概述#
Promise 是异步编程的一种解决方案,从语法上讲,Promise是一个对象,从它可以获取异步操作的消息。
使用 Promise 主要有以下好处:
⚫ 可以避免多层异步调用嵌套问题(回调地狱)
⚫ Promise 对象提供了简洁的API,使得控制异步操作更加容易
Promise 基本使用#
- 实例化
Promise
对象,构造函数中传递函数,该函数中用于处理异步任务 resolve
和reject
两个参数用于处理成功和失败两种情况,并通过p.then
获取处理结果通过Promise把 异步API同步化
var p = new Promise(function(resolve, reject){
// 这里用于实现异步任务
setTimeout(function(){
var flag = false;
if(flag) {
// 成功时调用
resolve('hello');
}else{
// 失败时调用
reject('出错了');
}
}, 100);
});
p.then(function(data){
// 从resolve得到正常结果
console.log(data)
},function(info){
// 从reject得到错误信息
console.log(info)
});
基于Promise处理Ajax请求#
关于then
的链式调用,可用来处理多个 ajax 请求,比如:
function queryData(url) {
var p = new Promise(function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState != 4) return;
if(xhr.readyState == 4 && xhr.status == 200) {
// 处理正常的情况
resolve(xhr.responseText);
}else{
// 处理异常情况
reject('服务器错误');
}
};
xhr.open('get', url);
xhr.send(null);
});
return p;
}
queryData('http://localhost:3000/data')
.then(function(data){
console.log(data) // Hello World!
/*
在then方法中,可以直接return 普通数据 或 Promise对象,
若返回 Promise对象作为后面的then(data)的调用者, data用来接收对象中异步执行结果
而普通数据也可以被后面的then(data) 中的 data接收
*/
return queryData('http://localhost:3000/data1');
})
.then(function(data){
console.log(data); // 输出 Hello TOM!
return queryData('http://localhost:3000/data2');
})
.then(function(data){
console.log(data) // 输出 Hello JERRY!
return 'abc'
})
.then(function(data){
console.log(data) // 输出 abc
})
;
Promise 基本API#
实例方法#
方法 | 描述 |
---|---|
p.then() | 得到异步任务 resolve()返回的正确结果 |
p.catch() | 获取异步任务 reject()返回的异常信息 |
p.finally() | 无论异步任务执行成功与否都会执行(尚且不是正式标准) |
注意:p.then()
既可以得到 异步的正确结果吗,也可以得到 错误结果
function foo() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// resolve(123);
reject('error');
}, 100);
})
}
foo()
.then(function (data) {
console.log(data)
})
.catch(function (data) {
console.log(data)
})
.finally(function () {
console.log('finished')
});
// 等效写法
// foo()
// .then(function(data) {
// console.log(data)
// }, function(data) {
// console.log(data)
// })
// .finally(function() {
// console.log('finished')
// });
对象方法#
方法 | 描述 |
---|---|
Promise.all() | 并发处理多个异步任务,所有任务都执行完成才能得到结果 |
Promise.race() | 并发处理多个异步任务,只要有一个任务完成就能得到结果 |
Promise.all
方法接受一个数组作参数,数组中的对象(p1、p2、p3)
均为 promise实例(如果不是一个promise,该项会被用Promise.resolve
转换为一个promise)。它的状态由这三个promise实例决定Promise.race
方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled
或rejected
),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数
// 调用上述的 queryData() 获取多个 包装了异步任务的promise对象
var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');
// 通过all()处理
Promise.all([p1,p2,p3]).then(function(result){
console.log(result);
// 输出一个数组,其中的每个元素为异步任务的执行结果 (3) ['Hello TOM!', 'Hello JERRY!', 'Hello SPIKE!']
})
// 通过 race() 处理
Promise.race([p1, p2, p3]).then(function(result) {
console.log(result) // 输出首个异步任务的执行完毕的结果: Hello TOM!
})
⑤ 异步函数
异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了
异步函数离不开 async、await
关键字, async/await是ES7引入的新语法,可以更加方便的进行异步操作
async关键字#
- 普通函数定义前加async关键字 普通函数变成异步函数
- 在异步函数内部使用
return关键字
进行结果返回,结果会被包裹的promise对象中, return关键字代替了resolve方法 - 在异步函数内部使用
throw关键字
抛出程序异常 , throw关键字替代了reject 方法 - 异步函数通过
return or throw
默认返回 包含返回结果的promise对象 - 调用异步函数再链式调用
then()
获取异步函数return
的结果 - 调用异步函数再链式调用
catch()
获取异步函数throw
的错误信息
await关键字#
- await关键字只能出现在异步函数中
await promise
,await后面只能写promise对象 写其他类型的API是不不可以的- await关键字可是暂停异步函数向下执行 ,直到promise返回结果
async function queryData() {
var ret = await new Promise(function(resolve, reject){
setTimeout(function(){
resolve('nihao')
// resolve('nihao1')
},1000);
})
// console.dir(ret)
if(ret == 'nihao'){
return ret; //Promise { 'nihao' }
}else {
throw 'error!' // Promise { 'error!' }
}
}
queryData().then(function(data){
console.log(data)
}).catch(function(err){
console.log(err);
})
作者:Hong•Guo
出处:https://www.cnblogs.com/ghnb1/p/15848420.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix