js单线程异步模型


单线程异步模型

线程

不像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....
  
  补充: 12 是互斥的,即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左侧的变量中。asyncawait需要成对出现,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)
    }
  }

文章作者: KarlFranz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source KarlFranz !
评论
  目录