JS中的同步异步编程
- 1、JS中的同步异步编程
/*
* JS中的同步异步编程
* 【进程、线程】
+ 进程:代表的是一个程序(浏览器新开一个页面就是一个进程)
+ 线程:用来处理进程中的具体事物的,如果一个程序中需要同时做好多事情,就需要开辟好多线程
+ 一个线程同时只能做一件事
* 浏览器是多线程的
+ GUI渲染线程
+ HTTP网络线程
+ JS代码渲染线程
+ ...
* JS是单线程的:浏览器只分配一个线程用来渲染JS代码
+ JS中的代码大部分都是“同步编程”:上面任务没有处理完成,下面的代码时无法进行处理的
+ 但是JS中利用浏览器的多线程机制,可以规划处“异步编程”效果
+ 定时器
+ ajax/Fetch/跨域(HTTP网络请求)
+ 事件绑定
+ Promise中也有异步编程
+ async / await
+ Generator yield
+ ...
*/
/*
* 计算程序执行的时间(预估)
+ 运行监控 console.time/timeEnd(受当前电脑运行环境的影响)
+ 大O表示法(提前预估)
*/
/* console.time('a');
for(let i=0; i<99999999; i++) {}
console.timeEnd('a'); */
/* while(true){}
console.log('Ok'); // 不执行,因为上面的死循环一直占用着“JS渲染线程”,线程空闲不下来,就处理不了其他的事情
// 真实项目中应避免死循环
*/
/*
* 定时器的异步编程
+ 设置定时器任务是同步的
+ “间隔interval这么长时间,执行定时器绑定的函数”这个任务是异步的
+ 遇到异步任务,浏览器不会等待它执行完,而是继续执行下面的同步任务;等到所有同步任务执行完成,时间也到达了执行的条件,才会把异步任务执行
*/
/* setTimeout(()=>{
console.log('Ok');
}, 1000);
console.log('NO'); */
// interval设置为0也不是立即执行
/* setTimeout(()=>{
console.log('OK')
},0);
console.log('NO'); */
/*
* 浏览器加载页面,默认开辟一个任务队列(是一个堆内存,不是线程)
Event Queue(存放异步任务)
+ 微任务
+ 宏任务:定时器绑定的方法(函数)
* 当栈中的同步任务或者其他任务没有执行完之前,JS渲染线程不会空闲下来,此时哪怕定时器已经到达指定时间,也不会执行(因为JS是单线程的)
* 异步任务队列中
+ 有微任务,先执行微任务
+ 没有微任务,再执行宏任务
+ 执行顺序,谁先到达时间先执行谁
+ 并且都是把异步任务放到栈内存中去执行
*/
setTimeout(()=>{ // 定时器1
console.log(1)
},20);
console.log(2);
setTimeout(()=>{ // 定时器2
console.log(3);
},10);
console.log(4);
console.time('a');
for(let i=0; i<90000000; i++) {}
console.timeEnd('a'); // 50 ms左右,这
console.log(5);
setTimeout(() =>{ // 定时器3
console.log(6);
},8);
console.log(7);
setTimeout(() =>{ // 定时器4
console.log(8);
},15);
console.log(9);
// 先执行同步任务:2 4 5 7 9
// 再执行异步任务:3 1 6 8
// 所以答案是:2 4 5 7 9 3 1 6 8
/*
* 解析:
+ 循环结束的时候前面定时器1和定时器2都已经到达指定时间了,但是不执行,因为同步任务还没有执行结束;
+ 同步任务执行结束,再执行异步任务:在存放定时器3的时候,定时器1和定时器2都已经到达指定时间了,也就是说定时1和定时器2已经满足执行条件,因此先执行,但是定时器2又比定时器1先到达时间,所以先执行定时器2,再执行定时器1;接着是定时器3和定时器4,谁先到达时间谁先被执行
*/
- 2、Promise的基础知识
/*
* Promise语法:因为Promise是内置类,所以使用的时候得通过new创建其实例
let p1 = new Promise([Fn]);
+ Promise 是一个内置类
+ p1 是类的一个实例
+ Fn 是回调函数(必须要传递)
*/
// let p = new Promise(); // 报错 Promise resolver undefined is not a function
/*
* Promise 本身是同步的(只是用来管理异步编程)
+ new Promise 的时候会立即把回调函数Fn立即执行,同时黑还会给Fn传递两个函数
+ resolve函数:函数执行会把当前实例的状态修改为成功,并且还会把执行resole函数时传递的值,赋值给实例的 [[PromiseValue]] 属性
+ reject函数:resolve函数:函数执行会把当前实例的状态修改为失败,并且还会把执行reject函数时传递的值,赋值给实例的 [[PromiseValue]] 属性
+ 这两个函数在同一个实例的回调函数中只会执行其中的一个,不会同时执行两个(哪怕两个都写了,第二个也不会执行)
+ 创建一个Promise实例,每个Promise实例有两个属性(很重要)
[promise实例]:有两个私有属性
+ [[PromiseStatus]]: 当前promise实例的状态
+ pendind:初始状态
+ fulfilled/resolved:成功状态(一般指的是异步请求成功)
+ rejected:失败状态(一般指的是异步请求失败)
+ 一旦状态从 pending 变为 fulilled 或者 rejected,那么实例的状态就不会再改变的,即状态改变的单向的(只能 pending -> fulilled 或者 pending -> rejected)
+ [[PromiseValue]]: 当前promise实例的结果/值
+ 初始值是undefined
+ 不论成功还是失败,成功的结果或者失败的原因都会赋值给它
*/
/* let p1 = new Promise(function(){
console.log(1);
});
console.log(2);
// 先输出1,再输出2 (证明Promise是同步的)
console.log(Object.prototype.toString.call(p1)); // '[object Promise]'
console.dir(p1);
*/
/* let p1 = new Promise(function(resolve, reject){
resolve(321); // 改变状态后,下面再改变状态就无用了
reject(123); // 这里不会执行,因为已经执行 resolve了
});
console.log(p1); */
/*
* Promise.prototype 上有几个重要方法
+ then([A,B]):基于then方法存放两个回调函数A、B,当实例的状态是成功则执行函数A;状态为失败则执行函数B
+ catch
+ finally
*/
let p1 = new Promise((resolve, reject)=> {
// Promise中的 resolve/reject 是异步任务(微任务)
console.log(a); // 报错 a is not defined => p1的状态为失败(rejected),值为失败的原因 'a is not defined'
resolve('OK');
// 同步任务执行完之后,去找异步任务 resolve,执行这个异步任务的时候
// + 先更改当前实例状态为 fulilled
// + 把 OK 赋值给 实例的 [[PromiseValue]]
// + 通知then方法中的第一个函数(A)执行
});
/*
* 执行then方法会返回一个新的Promise实例
+ 执行then是为了把当前实例成功执行的A以及失败执行的B存储起来
+ 同时返回一个新的Promise实例 p2
+ p2的结果和状态:由上一个实例p1基于then存放的A/B函数执行决定
+ 不论p1基于then执行A还是执行B,只要执行过程中浏览器没有报错,则p2的状态都为成功,否则就为失败
+ p2的值是p1基于then执行A或者B的返回值(如果没有返回值,就默认是undefined)
+ 特殊情况:如果A/B执行时返回了一个新的Promise实例,则返回的实例的状态和结果,会直接决定p2的状态和结果
p1的状态也会受到回调函数Fn执行是否报错的影响,如果执行报错了,则p1的状态就是失败的,值为失败的原因;如果执行不报错,则取决于Fn中执行的是resolve/reject的哪一个
*/
let p2 = p1.then(res =>{ // 状态为成功就会执行这个函数
console.log(res);
return 1234;
}, rej=>{ // 状态为失败才会执行这个函数
console.log(rej);
return a; // 这里报错,所以导致p2的状态为失败,值是失败的原因
});
console.log('哈哈'); // 先输出(这是同步任务)