单线程异步模型
线程
不像java, javascript 是单线程,同一时间只能做一件事, 那么在同一时间又多个任务的话,就需要排队,
前一个任务执行完,才会执行下一个任务。2
javascript为什么是单线程?:
*** javascript 单线程是为了解决很复杂的同步问题,
就比如一个线程在某个dom 节点增加内容,一个线程要删除这个dom 节点,那到底是要增加内容还是删除这个节点,这
就带来了很多复杂的问题。
同步异步
同步 (阻塞):
javascript 会严格按照单线程 (从上到下,从左到右) 执行。
var a = 1
var b = 2
var c = a + b
//这个例⼦总c⼀定是3不会出现先执⾏第三⾏然后在执⾏第⼆⾏和第⼀⾏的情况
console.log(c)
为什么需要异步?
javascript 本身是从上到下、从左往右的执行顺序,但是如果全部都是这种方式的话,网页的某些资源就必须得等待,
造成不佳的用户体验。(元素的渲染就是同步的任务)
因此,就有了异步出现的需要。
异步 (非阻塞) :
不加入主线程,加入任务队列,可以同时等待多个,待有返回值时、且执行栈被清空时进入执行栈执行!
javascript 是从上到下、从左到右的执行顺序,但是遇到异步代码不会立即执行,而是先挂起,等待所有同步代码
执行完毕再回来执行异步代码。
js线程
一张图来说明此等关系 :
js 是单线程的,但浏览器或者node 是多线程的,来辅助js线程的执行。
1. GUI 渲染线程
2. JS引擎线程
3. 定时器触发线程
4. 浏览器事件线程
5. http异步线程
6. EventLoop事件处理线程
7....
补充: 1 和 2 是互斥的,即GUI渲染线程会阻塞js 引擎线程的计算。
原因是GUI渲染线程在渲染时,若是js改变了dom,就会造成渲染的不同步。
线程与进程
电脑的任务管理器查看正在运行的程序,可以认为一个进程 ---> 就是在运行一个程序。
那么浏览器打开一个网页就是一个进程。
一个进程的运行需要很多线程的配合,比如打开QQ这个进程可能有接收消息线程、传输文件线程、检测安全线程...
因此一个网页能够正常的运行并且能够与用户交互,也需要多个线程的配合,如上 。
js引擎线程 (主线程)
主线程代码 (同步代码) 直接进入执行栈 (函数执行栈) 执行
异步代码交给对应的线程去执行。
例如:
1 var a = 2;
2 setTimeout(fun A)
3 ajax(fun B)
4 console.log()
5 dom.onclick(func C)
主线程在执行这段代码时,遇到 2 setTimeout(fun A) 交给 ***定时器触发线程去执行***,
遇到 3 ajax(fun B) 会交给 http ***异步线程去执行***,
碰到 5 dom.onclick(func C) ,会交给 ***浏览器事件线程*** 去执行。
这些不同的线程,为了方便,下面统一称为 ----> ****工作线程****。
工作线程 (不同的处理异步的 —> 浏览器线程)
这些线程 (工作线程) 主要做两件事,
执行主线程给过来的异步代码,
保存着回调函数,当工作线程中的定时任务时间到了,或网络请求数据返回了,将回调函数交给任务队列。
任务队列 (消息队列)
任务队列 (先进先出的数据结构) 保存着未来将要执行的函数,
可以理解为一个静态的队列存储结构,非线程!只做存储,里面存的是一堆异步成功后的回调函数,
先成功的排在队列前面,后成功的排在队列后面。
比如:
setTimeout(() => {
console.log(1)
}, 2000)
setTimeout(() => {
console.log(2)
}, 3000)
一开始任务队列是空的,两秒后,一个 () => { console.log(1) } 的函数进入队列,
在3秒后,一个 () => { console.log(2) } 的函数进入队列,此时队列里有两个元素,主线程从队列头中挨个取出并执行。
不同工作线程的区别
主线程把setTimeout、ajax、dom.onclick分别给三个线程,他们之间有些不同。
1、对于setTimeout,定时器触发线程 在接收到代码时就开始计时,时间到了将回调函数扔进队列。
2、对于ajax,http 异步线程 立即发起http请求,请求成功后将回调函数扔进队列。
3、对于dom.onclick,浏览器事件线程 会先监听dom,直到dom被点击了,才将回调函数扔进队列。
EventLoop线程
在执行栈被清空时触发事件循环。
事件循环会不断检测任务队列是否有任务(回调函数)可执行,有则进入执行栈执行,没有则结束本次事件循环,
开启下一轮事件循环,循环往复。
--------------------------------------------------------------------------
对于setTimeout,setInterval的定时,也不一定会按照设定的来,因为主线程代码可能很复杂,甚至要执行很久,
所以有时可能会发生你定时设置了3秒,但实际上是3.5s后执行(主线程花费了0.5s)。
函数执行栈 (执行栈)
执行栈是一个栈的数据结构(先进后出),执行栈执行的函数执行后,会出栈销毁,然后下一个进栈,出栈。
当有函数嵌套的时候,就会堆积栈帧。
function task1(){
console.log('task1执⾏')
task2()
console.log('task2执⾏完毕')
}
function task2(){
console.log('task2执⾏')
task3()
console.log('task3执⾏完毕')
}
function task3(){
console.log('task3执⾏')
}
task1() // 函数是同步执行的
console.log('task1执⾏完毕')
/*
task1执⾏
task2执⾏
task3执⾏
task3执⾏完毕
task2执⾏完毕
task1执⾏完毕
*/
// 函数作用域在函数声明时就已经决定了
// 函数调用时 创建函数执行上下文,该函数执行上下文被压入执行上下文栈
// 使用argument 创建活动对象、this 、作用域链,并将活动对象压入函数执行上下文顶部
// 初始化函数执行上下文
// 执行函数
// 函数执行完毕,从执行上下文中弹出
let a = 1
function fn1() {
console.log(a)
}
function fn2() {
let a = 2
fn1()
}
fn2() // 1
递归
我们经常会在未知深度的树形结构或者其他合适的场景使用递归,但这是有风险的。
递归函数可以看作是在一个函数嵌套n层执行,那么在执行过程中就会出现栈帧堆积,
如果处理的数据过大,可能出现执行栈的高度不够放置新的栈帧,而造成栈溢出的错误。
因此在做海量数据递归时值得注意这个问题!
递归的深度:
执行栈深度根据不同的浏览器和javascript引擎 有着不同的区别。
var i = 0;
function task(){
i++
console.log(`递归了${i}次`)
task()
}
task()
跨越递归限制:
var i = 0;
function task(){
i++
console.log(`递归了${i}次`)
//使⽤异步任务来阻⽌递归的溢出
setTimeout(function(){
task()
},0)
}
task()
原因:
现在我们不只是在栈中执行了,新的递归函数会进入工作线程,工作线程中的函数到时间后进入 --->
任务队列,事件循环检测到任务队列有可执行的函数,于是放入执行栈中执行,
执行完毕后出栈,然后新的递归函数再次进入 ---> 工作线程, 如此往复。
这样一来,执行栈中永远都只有一个函数在执行,不会栈溢出。
宏任务微任务
异步的代码分为宏任务和微任务,微任务先执行,宏任务是javascript最原始的异步任务,
包括setTimeout、setInterVal、AJAX等。
常见宏任务:
浏览器 node
I/O ✅ ✅
setTimeout ✅ ✅
setInterval ✅ ✅
setImmediate ❌ ✅
requestAnimationFrame ✅ ❌
// requestAnimationFrame 在MDN的定义为,下次⻚⾯重绘前
// 所执⾏的操作,⽽重绘也是作为宏任务的⼀个步骤来存在的,且该步骤晚于微任务的执⾏
微任务:
process.nextTick ❌ ✅
MutationObserver ✅ ❌
Promise.then catch finally ✅ ✅
setTimeout(function() {
console.log('timer1')
}, 0)
requestAnimationFrame(function(){
console.log('UI update')
})
setTimeout(function() {
console.log('timer2')
}, 0)
new Promise(function executor(resolve) {
console.log('promise 1')
resolve()
console.log('promise 2')
}).then(function() {
console.log('promise then')
})
console.log('end')
// promise 1
// promise 2
// end
// promise then
// timer1
// timer2
// UI update
// promise 1
// promise 2
// end
// promise then
// UI update
// timer1
// timer2
document.addEventListener('click', function(){
Promise.resolve().then(()=> console.log(1));
console.log(2);
})
document.addEventListener('click', function(){
Promise.resolve().then(()=> console.log(3));
console.log(4);
})
// 2,1,4,3
Promise
Promise 主要是为了解决异步回调地狱的问题。
她是将异步回调嵌套,拆解成链式调用,
这样便可以将代码按照上下顺序来进行异步代码的流程控制。
Pomise对象相当于⼀个未知状态的对象,他的定义就是声明⼀个等待未来结果的对象,在结果发⽣之前他⼀直是
初始状态,在结果发⽣之后他会变成其中⼀种⽬标状态,它的名字 Promise 中⽂翻译为保证。
因此 Promise 对象是一个严谨的对象,一定会如约执行!(使用不当除外)
Promise函数中既存在同步函数,也存在异步函数。
她的初始化函数即为同步函数,then catch finally 为异步函数。
promise三种状态
pending: 初始状态 (就绪状态), 此时promise仅做了初始化并注册了他对象上的所有任务。
fulfilled: 已完成,当初始化函数中执行reslove时,promise的状态变为fulfilled,同时then 函数中的回调函数开始执行,
relslove传递的函数会作为后面then 回调函数的形参。
若初始化函数中不使用 reslove, 那么then 中的回调也不会执行!
若初始化函数执行了两种状态,只会取最先执行的状态。
new Promise(function(resolve,reject){
}).then(function(){
console.log('then执⾏')
}).catch(function(){
console.log('catch执⾏')
}).finally(function(){
console.log('finally执⾏')
})
new Promise(function(resolve,reject){
resolve()
reject()
}).then(function(){
console.log('then执⾏')
}).catch(function(){
console.log('catch执⾏')
}).finally(function(){
console.log('finally执⾏')
})
链式调用
function MyPromise(){
return this
}
MyPromise.prototype.then = function(){
console.log('触发了then')
return this
}
new MyPromise().then().then().then()
本质是我们调用这些链式函数的结尾时,它又返回了一个 包含它自己的对象,或是 “新的自己”。
补充:
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
console.log(p);
p.then(function(res){
//该res的结果是resolve传递的参数
console.log(res)
}).then(function(res){
//该res的结果是undefined
console.log(res)
return '123'
}).then(function(res){
//该res的结果是123
console.log(res)
return new Promise(function(resolve){
resolve(456)
})
}).then(function(res){
//该res的结果是456
console.log(res)
return '我是直接返回的结果'
}).then()
.then('我是字符串')
.then(function(res){
//该res的结果是“我是直接返回的结果”
console.log(res)
})
/**
Promise {<fulfilled>: '我是Promise的值'}
VM111:7 我是Promise的值
VM111:10 undefined
VM111:14 123
VM111:20 456
VM111:26 我是直接返回的结果
Promise {<fulfilled>: undefined}
*/
/**
只要有then, 且触发了reslove,整个链条就会执行到结尾。
后续每个then 中的回调都可以return一个值,这个值作为下一个then 回调中的参数,
若返回的是一个Promise对象,这个对象中的reslove的结果就会是下一个then 中的形参,
若没有返回,那么下一个then中的函数接收到的就是undefined,
若then 中传入的 **不是函数** ,或没有传值,链式调用同样不会中断,
且在这之前最近的一次返回结果,会传入离它最近的then 的回调函数作为参数。
*/
中断链式调用:
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
console.log(p) p.then(function(res){
console.log(res)
}).then(function(res){
//有两种⽅式中断Promise
// throw('我是中断的原因')
return Promise.reject('我是中断的原因')
}).then(function(res){
console.log(res)
}).then(function(res){
console.log(res)
}).catch(function(err){
console.log(err)
})
中断链式调用是否违背promise精神?
问题: promise状态一但确定,就不会再改变,那为什么在初始化函数中执行了reslove ,此时promise 的状态就已经是
fulfilled了,但后面执行了中断操作,中断后promise的状态就是reject了,在同一个promise对象中出现了两种状态,
这并不科学!
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
var p1 = p.then(function(res){
})
console.log(p)
console.log(p1)
console.log(p1===p)
// Promise {<fulfilled>: '我是Promise的值'}
// VM277:7 Promise {<pending>}
// VM277:8 false
可以看到.then之后,是一个新的promise对象,与.then之前的 promise对象不是同一个对象,
且这个新对象的状态为 pending (初始状态),
那么在一个全新的promise对象中,正常执行 reslove 或 中断操作,
与它之前的promise对象已经确定的状态,
就没有什么关系了。
因此,并不违背!
Promise.all
有时我们请求数据需要用到上一个接口返回的数据、亦或是需要调用数个接口,待得他们全部返回,才开始渲染页面,
这时,若我们使用Promise.then 函数的异步控制,可以保证三个接口按照顺序调用,
但用then 就必须当下这一个接口完成才能调下一个,这样无疑增加了时间。
针对这个问题,Promsise增加一个all方法。
Promise.all([v1, v2, v3]).then(res => {
console.log('res', res);
}).catch(err => {
console.log('err', err);
})
将多个Promise包装成一个Promise实例,
成功时返回一个结果数组,
失败时返回最先被reject状态的值。
* ** 值得注意的是:
* Promise.all获得成功的结果数组里面的 ***数据顺序与接收到的数组顺序是一致的。
Promise.race
// race 使用格式与all相同
// 顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,
// 就返回那个结果,不管结果本身是成功状态还是失败状态
假设我们有一个播放视频的页面,为了确保用户的低延迟,一般都会有多个媒体数据源,
进入页面时,让多个数据源进行竞赛,
将那个速度最快,即延迟最低的数据源设置为用于用户播放视频的默认数据源。
Promise.race([v1, v2, v3]).then(res => {
console.log('res', res);
}).catch(err => {
console.log('err', err);
})
async await
我们发现 async await 的编写⽅式与Generator函数结构很相似,
提案中规定了我们可以使⽤async修饰⼀个函数,
这样就能在该函数的直接⼦作⽤域中,使⽤await来⾃动的控制函数的流程,await 右侧可以编写任何变量
或对象,当右侧是普通对象的时候函数会⾃动返回右侧的结果并向下执⾏,⽽当await右侧为Promise对象时,
如果Promise对象状态没有变成完成,函数就会挂起等待,直到Promise对象变成fulfilled,程序再向下执⾏,
并且Promise的值会⾃动返回给await左侧的变量中。async和await需要成对出现,async可以单独修饰函数,
但是await只能在被async修饰的函数中使⽤。
async function test(){
await ...
await ...
}
test()
------------------------------------------
async function test(){
console.log(3)
return 1
}
console.log(1)
test()
console.log(2)
// 1 3 2
async function test(){
console.log(3)
var a = await 4
console.log(a)
return 1
}
console.log(1)
test()
console.log(2)
// 1 3 2 4
------------------------------------
console.log(1)
new Promise(function(resolve){
console.log(3)
resolve(4)
}).then(function(a){
console.log(a)
})
console.log(2)
try
function MyPromise(fn) {
this.promiseState = "pending";
this.promiseValue = undefined;
let _this = this;
let reslove = function (value) {
if (_this.promiseState === "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
// 传入类型为promise对象时
if (value instanceof MyPromise) {
value.then((res) => {
_this.thenCallback(res);
});
} else {
// 传入的为基本数据类型时
setTimeout(() => {
if (_this.thenCallback) {
_this.thenCallback(value);
}
});
}
}
};
let reject = function (err) {
if (_this.promiseState === "pending") {
_this.promiseState = "rejected";
_this.promiseValue = err;
}
// catch 跨对象监听
setTimeout(() => {
if (_this.catchCallback) {
_this.catchCallback(err);
} else if (_this.thenCallback) {
_this.thenCallback(err);
} else {
throw "this promise is reject, but can not get catch";
}
});
};
if (fn) {
fn(reslove, reject);
} else {
throw "Init Error , Please use a function to init MyPromisr";
}
}
// MyPromise.prototype.then = function (callback) {
// let _this = this;
// // return 一个promise对象 ---> 支持链式调用
// return new MyPromise((reslove, reject) => {
// _this.thenCallback = (value) => {
// let callbackRes = callback(value);
// reslove(callbackRes);
// };
// });
// };
// 支持中断
MyPromise.prototype.then = function (callback) {
let _this = this;
return new MyPromise((reslove, reject) => {
_this.thenCallback = (value) => {
if (_this.promiseState === "rejected") {
reject(value);
} else {
let callbackRes = callback(value);
if (callbackRes instanceof MyPromise) {
if (callbackRes.promiseState === "rejected") {
callbackRes.catch((error) => {
reject(error);
});
}
} else {
reslove(callbackRes);
}
}
};
});
};
MyPromise.prototype.catch = function (callback) {
let _this = this;
return new MyPromise((reslove, reject) => {
_this.catchCallback = (value) => {
let callbackRes = callback(value);
reject(callbackRes);
};
});
};
MyPromise.reject = function (error) {
return new MyPromise((reslove, reject) => {
reject(error);
});
};
// then
// const p = new MyPromise((reslove, reject) => {
// reslove("123");
// });
// console.log("p", p);
// p.then((res) => {
// console.log("res--->", res);
// return 'what'
// })
// .then(res => {
// console.log('第二次链式', res);
// })
// ;
// catch
// const p = new MyPromise((reslove, reject) => {
// reject("出错了");
// });
// p.then((res) => {
// console.log("res--->", res);
// return 'what'
// })
// .catch(res => console.log('错误---》', res))
// 中断
// var p = new MyPromise(function (resolve, reject) {
// resolve(123);
// });
// console.log(p);
// p.then(function (res) {
// console.log("then1执⾏");
// return 456;
// })
// .then(function (res) {
// console.log("then2执⾏");
// return MyPromise.reject("中断了");
// })
// .then(function (res) {
// console.log("then3执⾏");
// return 789;
// })
// .then(function (res) {
// console.log("then4执⾏");
// return 666;
// })
// .catch(function (err) {
// console.log("catch执⾏");
// console.log(err);
// });
MyPromise.prototype.all = function (promiseArr) {
let resArr = [];
let errValue = undefined;
let isRejected = false;
return new MyPromise(function (reslove, reject) {
for (let i = 0; i < promiseArr.length; i++) {
(function (i) {
promiseArr[i]
.then((res) => {
resArr[i] = res;
let allDone = promiseArr.every((item) => {
return item.promiseState === "fulfilled";
});
if (allDone) {
reslove(resArr);
}
})
.catch((err) => {
isRejected = true;
errValue = err;
reject(err);
});
})(i);
if(isRejected){
break
}
}
});
};
MyPromise.prototype.race = function(promiseArr) {
let end = false
for(let i = 0; i < promiseArr.length; i++) {
(function(i){
promiseArr[i].then(res => {
if(end === false) {
end = true
reslove(res)
}
})
.catch(err => {
if(end === false) {
end = true
reject(err)
}
})
})(i)
}
}