JavaScript 异步编程
❝掌握JavaScript主流的异步任务处理 ( 本篇文章内容输出来源:《拉钩教育大前端训练营》参阅《你不知道的JavaScript中卷》异步章节)
❞
JavaScrip 采用单线程模式工作的原因,需要进行DOM操作,如果多个线程同时修改DOM浏览器无法知道以哪个线程为主。
JavaScirpt分为:同步模式、异步模式
同步模式与异步模式
同步模式
同步模式其实就是:排队执行,下面根据一个Gif动画来演示同步模式,非常简单理解,js维护了一个正在执行的工作表,当工作表的任务被清空后就结束了。
如下打开调试模式,注意观察Call Stack
调用栈的情况,当执行foo
方法的是否foo
会进入Call Stack
调用栈之后打印'foo task',
然后执行bar()
方法bar
进入调用栈打印'bar task'
,bar
执行完后被移除调用栈,foo
被移除调用栈然后打印'global end'
执行结束。
存在的问题:如果其中的某一个任务执行的时间过长,后面的任务就会被阻塞,界面就会被卡顿,所以就需要使用异步模式去执行避免界面被卡死。
异步模式
通过一个图来演示异步任务,用到事件循环与消息队列机制实现
Promise异步方案
常见的异步方案就是通过回调函数来实现,导致回调地狱的问题,CommonJS社区提出了Promise方案并在ES6中采用了。如下代码实现一个环绕动画如果通过回调会嵌套多次。
案例演示地址
let box = document.querySelector('#box');
move(box, 'left', 300, () => {
move(box, 'top', 300, () => {
move(box, 'left', 0, () => {
move(box, 'top', 0, () => {
console.log('运动完成');
});
});
});
});
Promise 的使用案例演示代码如下:
//应用案例
function ajax(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
}
xhr.send();
});
}
let promise2 = ajax('./api/user.json');
let newPromise = promise2.then((res) => {
console.log(res);
});
console.log(promise2 === newPromise);//false 每一个then都返回一个新的promise对象
//then 仍然会导致回调地狱 尽量保证异步任务的扁平化
//也可以在then方法中返回一个promise对象
ajax('./api/user.json').then(res=>{
console.log(111);
return ajax('./api/user.json');
}).then(res=>{
console.log(222);
return 'foo';
}).then(res=>{
console.log(res);
})
//OUT:
false
Array(2)
111
222
foo
Promise 链式调用注意一下几点
-
Promise对象的then方法会返回一个全新的Promise对象 -
后面的then方法就是在为上一个then返回的Promise注册回调 -
前面then方法中回调函数的返回值会作为后面then方法回调的参数 -
如果回调中返回的是Promise,那后面then方法的回调会等待它的结束
Promise异常处理
Promise
执行过程中出现错误onRejected
回调会执行,一般通过catch
方法注册失败回调,跟在then方法第二个参数注册回调结果是一样的。
const promise = new Promise(function (resolve, reject) {
//只能调用两者中的一个 一旦设置了某个状态就不允许修改了
// resolve(100);//成功
reject(new Error('promise rejected'));//失败
});
promise.then(function (value) {
console.log('resolved', value);
}, function (err) {
console.log('rejected', err);
}).catch(err=>{
console.log("catch",err);
});
console.log('end');
推荐使用catch
方法作为错误的回调,不推荐使用then
方法的第二个参数作为错误回调,原因如下:
当我们在收到正确的回调又返回一个Promise
对象但是在执行过程中出现了错误,而这时无法收到错误回调的。
ajax('./api/user.json').then(res=>{
console.log('onresolved',res);
return ajax('/error.json');
},err=>{
console.log("onRejected",err);
});
我们再来看catch
方法:
ajax('./api/user.json').then(res=>{
console.log('onresolved',res);
return ajax('/error.json');
}).catch(err=>{
console.log("onRejected",err);
});
打印结果如下:catch
方法可以捕捉到then
方法return
的新的Promise
对象的执行错误。
onresolved (2) [{…}, {…}]
onRejected Error: Not Found
at XMLHttpRequest.xhr.onload (promise.js:28)
除此之外全局对象注册unhandlerdrejection 事件,处理代码中没有被手动捕获处理的异常。下面是node中的方法
process.on('unhandledRejection',(reason,promise)=>{
//reason => Promise 失败原因,一般是一个错误对象
//promise => 出现异常的Promise对象
})
一般不推荐使用,应该在代码中明确捕获每一个可能的异常,而不是丢给全局处理
Promise 的静态方法
//一个成功状态的Promise 对象
Promise.resolve('foo').then(res=>{
console.log(res);
});
var promise = ajax('./api/user.json');
var promise2 = Promise.resolve(promise);//如果传入一个Prmose对象会原样返回相同的Promise对象
console.log(promise === promise2);//true
//如下传入的一个对象带有then方法的对象一样可以执行
Promise.resolve({
then:function(onFulfilled,onRejected){
onFulfilled('f00');
}
}).then(res=>{
console.log(res);//f00
});
//创建一个失败状态的Promise对象
Promise.reject(new Error('rejected')).catch(err=>{
console.log(err);
})
Promise并行执行:all
race
将多个Promise对象组合到一起
var promise = Promise.all([ajax('./api/user.json'),ajax('./api/user.json')]);
promise.then(res=>{
console.log(res);
})
//都成功才会成功 有一个失败就会返回失败状态回调
ajax('./api/user.json')
.then(res=>{
const urls = Object.values(res);
console.log('??',urls);
const tasks = urls.map(url=>{
console.log(url);
return ajax(url);
});
console.log(tasks);
return Promise.all(tasks);
}).then(res=>{
console.log(res);
});
//race 只会等待第一个结束的任务
const request = ajax('./api/user.json');
const timeout = new Promise((resolve,reject)=>{
setTimeout(() => {
reject(new Error('timeout'));
}, 500);
});
Promise.race([request,timeout]).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
});
模仿网络慢的情况,可以看到race会执行reject
Promise 执行时序:宏任务与微任务
Promise的回调会作为微任务执行。微任务:提高整体的响应能力。目前大部分异步回调作为宏任务
常见的宏任务与微任务如下图所示:
下面是JavaScript执行异步任务的执行时序图:
看下面的例子来进行理解: 下列例子中输出: 2 4 1 3 5
这其实也符合了上图事件循环的原理,先主任务执行输出: 2 4 之后查询是否有微观任务没有就新建宏观任务执行
然后宏观任务执行输出:1 3
之后查询是否之后查询是否有微观任务没有就新建宏观任务执行
执行输出: 5
let time = 0;
setTimeout(()=>{
time = 1;
console.log(time);
//宏任务嵌套宏任务
setTimeout(()=>{
time = 5;
console.log(time);
},1000);
},1000);
time = 2;
console.log(time);
setTimeout(()=>{
time=3;
console.log(time);
},1000);
time = 4;
console.log(time);
下面我们在看一个带有微任务的例子: 下面例子输出的结果: 2 4 6 1 3 5
.主任务执行完毕之后先执行微任务.
let time = 0;
setTimeout(()=>{
time = 1;
console.log(time);
//宏任务嵌套宏任务
setTimeout(()=>{
time = 5;
console.log(time);
},1000);
},1000);
time = 2;
console.log(time);
setTimeout(()=>{
time=3;
console.log(time);
},1000);
time = 4;
console.log(time);
//微任务
let observer = new MutationObserver(()=>{
time = 6;
console.log(6);
});
observer.observe(document.body,{
attributes:true
});
document.body.setAttribute('kkb',Math.random());
Generator异步方案
首先需要连接一下迭代器的
「迭代器」
❝for...in : 以原始插入的顺序迭代对象的可枚举属性for...of : 根据迭代对象的迭代器具体实现迭代对象数据 可迭代对象 - 实现了[Symbol.iterator]方法数组结构有[Symbol.iterator]方法,但是如果要迭代Object就需要添加[Symbol.iterator]方法的实现如下代码:
❞
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
//for of/ for in 迭代器
//for...in : 以原始插入的顺序迭代对象的可枚举属性
//for...of : 根据迭代对象的迭代器具体实现迭代对象数据 可迭代对象 - 实现了[Symbol.iterator]方法
let arr = ['a','b','c','d'];
let obj = {
a:1,
b:2,
c:3
}
for(let attr in arr){
console.log(attr);//0 1 2 3
}
for(let val of arr){
console.log(val);//a b c d
}
console.dir(arr);//symbol(Symbol.iterator): ƒ values()
console.dir(obj);//没有Symbol.iterator方法
//如果要对象使用for of需要加一个属性 自定义迭代器
obj[Symbol.iterator] = function(){
//迭代协议
//将对象value转换为数组
let values = Object.values(obj);
//将对象key转为数组
let keys = Object.keys(obj);
// let values = [...obj];
console.log(values);
let index = 0;
//必须返回一个对象 同时必须有一个next方法
return {
next(){
//done:表示循环是否完成
//value:for a of obj a就是value
//必须返回一个对象
if (index >= values.length) {
return{
done:true
}
}else{
return{
done:false,
value:{
key:keys[index],
value:obj[keys[index++]]
}
}
}
}
}
}
//可以测试直接迭代方法
let objIterator = obj[Symbol.iterator]();
//执行next方法
console.log(objIterator.next());
//其实for...of 一直调用objIterator.next() 直到done:true就会停止
for(let o of obj){
//obj is not iterable
console.log(o);
}
</script>
</html>
❝Generator函数比普通函数多了一个*号,函数内部使用yield语句,定义遍历器的每个成员,即不同的内部状态. 实现可迭代的函数.Generator函数一般很少会使用了解即可.
❞
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
/**
* 定义可迭代函数 yield 每次迭代的返回值
*/
function* fn() {
yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log("a");
resolve("1");
}, 500);
});
yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log("b");
resolve("2");
}, 500);
});
yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log("c");
resolve("3");
}, 500);
});
}
let f = fn();
// console.log(f.next());
// for (const iterator of f) {
// console.log(iterator);
// }
function co(fn) {
let f = fn();
next();
function next(data){
let result = f.next();
console.log(result);
if(!result.done){
//上一个异步执行完毕
result.value.then((info)=>{
console.log(info,data);
next(info);
});
}
}
}
co(fn);
// for (let fn of f) { 一同执行 不能异步调用
// }
</script>
</html>
Generator 生成器函数的使用
//Generator 生成器函数
function* foo() {
try {
console.log('start');
const res = yield 'foo';
console.log(res);
} catch (e) {
console.log(e);
}
}
const generator = foo();
const result = generator.next();
console.log(result);
generator.next('bar');
generator.throw(new Error('Generator Error'));//抛出一个异常
Generator 异步使用的案例如下代码:
function* main() {
try{
const users = yield ajax('./api/user.json');
console.log(users);
const posts = yield ajax('./api/user.json');
console.log(posts);
}catch(e){
console.log(e);
}
}
//通用的异步生成器方法
function co(generator){
const g = generator();
function handleResult(result){
if(result.done) return;
result.value.then(data=>{
handleResult(g.next(data));
}).catch(err=>{
g.throw(err);
})
}
handleResult(g.next());
}
co(main);
Async/Await 语法糖
推荐使用异步编程的标准.需要注意await 后面必须是一个Promise对象,await只能出现在async函数内部目前还不支持(以后可能会支持)
async function main2() {
try{
const users = await ajax('./api/user.json');
console.log(users);
const posts = await ajax('./api/user.json');
console.log(posts);
}catch(e){
console.log(e);
}
}
main2();
Promise 源码手写实现
1. Promise 是一个类 在执行这个类的时候 需要传递一个执行器进去 这个执行器会立即执行
2. Promise 中有三种状态分别为:pending -> fulfilled pending->rejected
3. resolve reject 函数用来更改状态
resolve:fulfilled
reject:rejected
4. then方法内部做的事情就是判断状态 如果状态成功调用成功回调函数
如果状态失败就回调失败的回调函数
5. then成功或失败都有一个参数分别表示成功的值和失败的原因
6. 记录成功的值和失败的值
7. 处理执行器内部异步情况的处理 调用resolve或reject
8. 处理then方法可以被多次调用
9. then方法可以被链式调用 后面then方法回调函数拿到的值是上一个then方法
回调函数的返回值
10. then 返回值是普通值还是Promise对象
11. then 返回相同的Promise对象循环调用的判断
12. 执行器内部发生错误 回调给reject,then 内部发生错误的处理
13. then无参数的链式调用实现
14. all等静态方法实现
const PENDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject);//执行器立即执行
} catch (e) {
this.reject(e);
}
}
status = PENDING;//定义状态
//成功之后的值
value = undefined;
//失败之后的值
error = undefined;
//成功回调
onFulfilled = [];
//失败回调
onRejected = [];
//箭头函数 this指向不会被更改 this还会指向MyPromise对象
resolve = (value) => {
//0 判断状态是不是pending 阻止向下执行
if (this.status !== PENDING) {
return;
}
//1 状态更改
this.status = FULFILLED;
//2 保存成功之后的值
this.value = value;
//3 成功回调是否存在
// this.onFulfilled && this.onFulfilled(this.value);
while (this.onFulfilled.length) {
this.onFulfilled.shift()();
}
}
reject = (error) => {
//0 判断状态是不是pending 阻止向下执行
if (this.status !== PENDING) {
return;
}
//1 状态更改
this.status = REJECTED;
//2 保存失败之后的值
this.error = error;
//3 失败回调是否存在
// this.onRejected && this.onRejected(this.error);
while (this.onRejected.length) {
this.onRejected.shift()();
}
}
then(onFulfilled, onRejected) {
onFulfilled = onFulfilled ? onFulfilled : value => value;
onRejected = onRejected ? onRejected : error => { throw error };
//1. 实现链式调用
let p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
//拿到回调函数的返回值
let result = onFulfilled(this.value);
//传递给下一个Promise对象
//判断result是普通值还是Promise对象
//如果是普通值 直接调用resolve
//如果是promise对象 查看promise对象返回的结果
//再根据promise对象返回的结果 决定调用resolve还是reject
// resolve(result);
//需要等待同步代码执行完毕拿到p在执行
this.resolvePromise(p, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.status == REJECTED) {
setTimeout(() => {
try {
//拿到回调函数的返回值
let result = onRejected(this.error);
this.resolvePromise(p, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
//由于异步代码没有立即执行 先存储回调 等异步代码执行完成后再执行回调
this.onFulfilled.push(() => {
setTimeout(() => {
try {
//拿到回调函数的返回值
let result = onFulfilled(this.value);
this.resolvePromise(p, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejected.push(() => {
setTimeout(() => {
try {
//拿到回调函数的返回值
let result = onRejected(this.error);
this.resolvePromise(p, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return p;
}
resolvePromise(p, result, resolve, reject) {
if (p === result) {
return reject(new TypeError('TypeError: Chaining cycle detected for'));
}
if (result instanceof MyPromise) {
//Promise对象 把新的Promise对象的值传递下去
result.then(resolve, reject);
} else {
//普通值
resolve(result);
}
}
static all(array) {
let result = [];
let index = 0;
return new MyPromise((resolve, reject) => {
function addData(key, value) {
result[key] = value;
index++;
if (index === array.length) {
//需要等待异步操作完成再调用resolve
resolve(result);
}
}
for (let i = 0; i < array.length; i++) {
let cur = array[i];
if (cur instanceof MyPromise) {
cur.then((value) => {
addData(i, value);
}, (err) => {
reject(err);
});
} else {
addData(i, cur);
}
}
});
}
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
static reject(error){
return new MyPromise((resolve,reject)=>{
reject(error);
})
}
finally(callback){
return this.then(res=>{
return MyPromise.resolve(callback()).then(()=>res);
},err=>{
return MyPromise.resolve(callback()).then(()=>{throw err});
});
}
catch(error){
return this.then(undefined,error);
}
}
//
module.exports = MyPromise;
本文分享自微信公众号 - FrontMagic(JakePrim)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
百度安全论文入选IEEE TIFS,让攻击者逃不出“楚门的世界”
导读:近日百度安全发表的论文《Detecting Hardware-assisted Virtualization with Inconspicuous Features》入选国际TOP期刊IEEE TIFS,论文深度剖析了虚拟化检测技术,并创新性提出一种最新硬件虚拟化检测技术,无须提权就能实现对硬件虚拟化环境的检测,本文将对这篇论文进行详细的解读。 虚拟化作为云计算系统中的一种基础技术,近年来,虚拟化技术不仅广泛应用于云服务器,也广泛应用于个人桌面。那么究竟虚拟化技术是什么,又为什么起到这么重要的作用呢? 想象两个场景: 空旷的厂房,整个楼层没有固定的墙壁,从事各式工种的工人和机器设备扎堆聚集,无法形成流水化的高效作业。 开放的冷藏库里,面包、龙虾和榴莲裸露的存储在一起,没有任何封装和隔离,长久下去面包有了龙虾味儿,龙虾有了榴莲味。 从这两个例子里,我们不难看出,在空间资源一定的条件下,需要根据不同的需求进行重新规划,已充分发挥最大的利用效率。在计算机领域,就存在一种技术可以解决上面的问题,那就是"虚拟化技术"。 虚拟化(Virtualization)技术最早出现在20世纪60年代的...
- 下一篇
通俗易懂的目标检测 | RCNN, SPPNet, Fast, Faster
插播新闻:最近建立了一个AI初学者微信群,如果你也是AI爱好者,加入我们吧~回复【入群】加入大家庭! 全文5500个字,22幅图,学习时长预计20分钟 目录 0概述 1RCNN 1.1 候选区Region Proposal 1.2 特征提取 1.3 SVM分类 1.4 线性回归 2 SPP Net 3 Fast RCNN 4 Faster RCNN 5 总结 0 概述 本文主要讲一下深度网络时代,目标检测系列的RCNN这个分支,这个分支就是常说的two-step,候选框 + 深度学习分类的模式:RCNN->SPP->Fast RCNN->Faster RCNN 另外一个分支是yolo v1-v4,这个分支是one-step的端到端的方式。不过这里主要是介绍RCNN那个体系的。 把下图中的yolo忽略,剩下的四个就是RCNN体系的一个非常好的总结。 1 RCNN RCNN既然是two-step,那么就从这里切入理解: 第一步,生成候选区 第二步,判断每个候选区的类别 2.1 用CNN提取特征 2.2 用SVM分类 2.3 用线性回归矫正候选框的位置 1.1 候选区Reg...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS8安装Docker,最新的服务器搭配容器使用
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题