• 异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise


    异步编程解决方案 Generator生成器函数、iterator迭代器、async/await、Promise

    Generator生成器函数

    面试题

    • generator用来做什么, generator函数的使用场景
    • 介绍下es6 generator函数
    • generator 实现 async await

    Generator生成器函数

    语法:function * 函数名(){}

    是什么
    Generator 函数是 ES6 提供的一种异步编程解决方案。内部可以看成一个状态机,返回迭代器对象,调用next方法进入下一个状态。yield表达式时暂停的标志

    使用的场景

    1. Generator 函数会返回一个迭代器对象,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
    2. async/await是自带执行器的Generator对象

    Generator生成器函数使用上的补充 了解

    1. generator函数可以用next方法来传参,该参数就会被当作上一个yield表达式的返回值。yield表达式本身没有返回值,返回undefined
      所以第一次next传参是没用的,只有从第二次开始next传参才有用

    2. Generator生成器函数如果有返回值 则是最后一次next的返回值{value:xxx:done:true}

    function* gen() {
      yield 1
      yield 2
      yield 3
    }
    const g = gen()
    console.log(g.next()) // { value: 1, done: false }
    console.log(g.next()) // { value: 2, done: false }
    console.log(g.next()) // { value: 3, done: false }
    console.log(g.next()) // { value: undefined, done: true }
    
    // 有return值
    function* gen() {
      yield 1
      yield 2
      yield 3
      return 4
    }
    const g = gen()
    console.log(g.next()) // { value: 1, done: false }
    console.log(g.next()) // { value: 2, done: false }
    console.log(g.next()) // { value: 3, done: false }
    console.log(g.next()) // { value: 4, done: true }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. yield后面跟promise,函数立即执行
    function f1(val){
      return Promise.resolve(val);
    }
    function f2(val){
      return new Promise(resolve=>{
        setTimeout(()=>{resolve(val)},1000);
      })
    }
    function* gen() {
      yield f1(1);
      yield f2(2);
      return 'ending';
    }
    const g = gen()
    console.log(g.next()) // { value: Promise { 1 }, done: false }
    console.log(g.next()) // { value: Promise {  }, done: false }
    console.log(g.next()) // { value: 3, done: true }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    基于Promise对象的简单自动执行器

    function f1(val){
      return Promise.resolve(val);
    }
    function f2(val){
      return new Promise(resolve=>{
        setTimeout(()=>{resolve(val)},1000);
      })
    }
    function* gen() {
      console.log(yield f1(1)); //1
      console.log(yield f2(2)); //2
      console.log(yield 'xxx');//'xxx'
      return 'ending';
    }
    
    
    function run(gen){
      var g = gen();
      function next(data){
        var result = g.next(data); //{ value: Promise { 1 }, done: false }
        if (result.done) return result.value; //执行完毕就可以返回
        Promise.resolve(result.value).then(function(data){ //获取promise的执行结果
          console.log(data); //1,2,xxx
          next(data); //将1作为yieId f1()执行的结果
        });
      }
    
      next();
    }
    run(gen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    iterator迭代器

    iterator迭代器

    集合概念有数组、对象、Map、Set,需要有一个统一的接口机制来处理所有不同的数据结构

    是什么
    迭代器iterator是一种接口,为不同的数据结构提供统一的访问机制

    好处

    • 为各种数据结构,提供一个统一的、简便的访问接口
    • 任何数据结构只要部署 Iterator 接口,就可以完成for..of遍历操作
    • 使得数据结构的成员能够按某种次序排列

    原理是什么?
    迭代器对象,有一个next方法,每次调用next方法都将返回一个结果。结果值是一个object {value:xxx,done},value表示具体的返回值, done 是布尔类型的,表示集合是否遍历完成。
    内部会维护一个指针,用来指向当前集合的位置,每调用一次 next 方法,指针都会向后移动一个位置。

    // 如果需要实现逆序:i初始化为items.length-1,依次i--
    //[Symbol.iterator] = createIterator
    function createIterator(items) {
      var i = 0;
      return {//迭代器对象,它具有一个 next 方法,该方法会返回一个对象,包含 value 和 done 两个属性
        next: function () {
          var done = i >= items.length;
          var val = !done ? items[i++] : undefined;
          return {
            done: done,
            value: val
          }
        }
      }
    }
    
    //测试
    var it = createIterator(['a', 'b', 'c']);
    console.log(it.next());// {value: "a", done: false}
    console.log(it.next());// {value: "b", done: false}
    console.log(it.next());// {value: "c", done: false}
    console.log(it.next());// "{ value: undefined, done: true }"
    console.log(it.next());// "{ value: undefined, done: true }"
    console.log(it.next());// "{ value: undefined, done: true }"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    可迭代对象

    1. 可迭代的数据内部都有[Symbol.iterator]的属性,也称为实现了Iterator接口
    2. [Symbol.iterator]的属性会返回一个函数createIterator函数,创造迭代器对象的方法
    3. [Symbol.iterator]返回的函数执行之后会返回一个迭代器对象
    4. [Symbol.iterator]函数返回的迭代器对象中有一个名称叫做next的方法
    5. next方法每次执行都会返回一个对象{value: 10, done: false}
    6. 这个对象中存储了当前取出的数据和是否取完了的标记

    使用场景

    1. for-of 遍历
    2. 扩展运算符(…)也会调用默认的 Iterator 接口。
    3. 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。

    async/await

    面试题

    • async / await 的特点以及使用场景
    • 为什么async/await要成对出现,只有await可以吗?
    • async/await内部通过什么来实现的
    • 说一下promise和async/await,分别是怎么捕获错误的?如果把promise写在try/catch里面会捕获到错误吗?
    • await/async与generator函数的区别

    async/await是什么? 使用场景是什么?

    是什么
    async函数是自带执行器的generator函数,是generator函数的语法糖,当函数执行遇到await时候, 函数会交出执行权。

    有什么作用
    async/await是回调地狱的最佳解决方案,以同步的方式去写异步代码。

    async函数返回值的状态
    async函数返回的Promise对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。

    await/async与generator函数的区别

    1. 自带执行器的generator函数,不需要通过next()到下一个状态
    2. async函数的返回值是promise,generator返回值是Iterator迭代器对象
    3. await命令后面,可以是 Promise 对象和原始类型的值,await可以看成是then的语法糖。yield命令后面只能是 Thunk 函数或 Promise 对象,不能是原始类型的值。

    await/async内部实现原理 Generator函数和自动执行器

    1. 用async实现Generator函数
    function f1(val){
      return Promise.resolve(val);
    }
    function f2(val){
      return new Promise(resolve=>{
        setTimeout(()=>{resolve(val)},1000);
      })
    }
    //async函数写法
     async function gen () => {
      console.log(await f1(1)); //1
      console.log(await f2(2)); //2
      console.log(await 'xxx');//'xxx'
      return 'ending';
    }
    //Genrator函数写法
    function* gen() {
      console.log(yield f1(1)); //1
      console.log(yield f2(2)); //2
      console.log(yield 'xxx');//'xxx'
      return 'ending';
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. async是自带执行器的Generator 函数

    async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

    
    async function fn(args){
    		//.....代码
    }
    //等价于
    //将Generator函数 添加自动执行器 变成async函数
    function fn(args){
    	return run(function*(){//run函数就是自动执行generator函数
    		//.....代码
    		})
    	})
    }
    
    //和之前的建议版本一样,增加了检测,next的参数从data变成了函数
    function run(genF) {
      return new Promise((resolve, reject) => {
          const result = genF(); //获取迭代器对象
          function getState(nextF) {
               let obj;
               try {
                obj = nextF();//执行迭代器函数 获取value {value:xxx.done:xxx}
               } catch(e) {
                   return reject(e);
               }
       
               if(obj.done) { //迭代器是否执行完毕
                   return resolve(obj.value);
               }
       
               Promise.resolve(obj.value).then(function(val){
                   getState(function(){ return result.next(val) });
               }, function(e){
                   getState(function(){ return result.throw(e)});
               });
          }
    
          getState(() => result.next()); //开始执行迭代器
      });
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    async错误捕获方式

    async错误捕获方式

    • await只能得到成功的结果,失败的结果需用try-catch
    function f1(val){
      return Promise.reject(val*2);
    }
    
    
    async function fn(){
      try {
       await f1(3);
      } catch (error) {
       console.log(error); //6
      }
    }
    
    fn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • try catch只能捕获同步代码,不能捕获异步代码,在async函数内使用await之后相当于异步代码变成了同步代码。

    try catch为什么不可以捕获到异步代码
    try catch是同步代码在执行栈中,异步任务会被推进队列中,根据事件循环机制,会先将执行栈中的代码执行完毕再去执行队列中的任务。

    Promise

    面试题

    • promise.finally的使用和then、catch有什么不同
    • promise.catch原理如何实现的
    • promise怎么做到链式调用
    • 口述promise实现过程
    • Promise 的异常处理机制
    • promise和await、async的区别
    • promise 三种状态 promise的方法有哪些
    • promise原理

    promise概述

    是什么
    promise是异步编程解决方案之一,从语法来说Promise是一个构造函数

    好处

    1. 指定回调函数的方式更加灵活
      纯回调的方式,执行结束就得不到状态了,必须在执行异步操作之前指定对应的回调函数,先注册回调再执行异步
      promise的方式可以将状态暂存,所以成功与失败的回调函数可以之后再指定
    2. 支持链式调用,可以解决回调地狱问题

    状态

    • pending:初始化状态
    • resolved:成功的状态
    • rejected:失败的状态
      ① pending转化为resolved,调用resolve(value)
      ② pending转化为rejected,调用rejecte(reason)或者抛出异常

    捕获错误的方式

    • .then()的第二次参数
    • .catch()方法

    promise知识点 了解

    Promise的实现原理

    首先需要知道几个问题

    状态相关

    1. 状态改变与指定回调函数的顺序
    • 先改变状态,同时指定数据,此时回调函数没有指定,需要先保存状态。
    • 先指定回调函数,后改变状态和指定数据,此时不知道调用哪个回调,要将回调函数保存
    1. promise的状态如何改变
    • 执行resolve(value): 如果是pending就会变为resolved
    • 执行reject(reason)或抛出异常: 如果是pending就会变为rejected

    构造器

    1. 需要变量保存回调函数和当前promise的状态
    2. 执行器函数是同步执行的,也就是会立即执行。执行器函数接受两个参数resolve和reject函数。
      resolve函数的作用 – reject函数基本差不多
      ①如果当前promise不是pending不做处理直接返回,如果是resolve函数的作用是将pending改为resolved
      ②保存resolve函数指定的数据
      ③如果此时有待执行的回调函数那么依次异步执行成功的回调

    then原理-实现链式调用

    • 参数有两个,一个成功的回调函数onResolved和失败的回调函数onRejected
    • 返回一个新的Promise对象,该Promise的状态由then的执行结果决定
    1. 返回promise对象,promise的结果由.then的回调函数决定
    • 如果抛出异常,新promise变为rejected,reason为抛出的异常
    • 如果返回的是非promise的任意值,新promise变为resolved,value为返回值。
    • 如果返回的是另一个新promise,此promise的结果由新promise的结果决定。
    1. .then指定回调时,考虑当前的状态,如果此时状态已经改变了说明是先改变状态后指定的回调,回调函数异步调用。如果此时状态还是pending状态,说明是先指定回调后改变状态,此时需要把回调函数存起来,待状态改变之后再调用
    2. 所以不管是先改变状态还是先指定回调,当最后回调被触发时,都需要获取回调函数的结果,因为需要根据回调函数的结果改变返回的promise的状态。
    3. .then 可以不传失败的回调,那么需要将异常传递下去。如果成功的回调不是函数,将value值传递下去。(catch只会处理失败,所以成功的要继续向后传递成功)

    promise.then、catch、finally的原理与实现

    类型描述
    promise.then可以指定成功和失败的回调
    promise.catch用于指定失败的回调,是特殊的then方法promise.then(undefined,onReject)
    promise.finally无论成功和失败都会调用,并且将值和状态原封不动的传递给后面的then

    promise.then的实现原理

    1. 参数是两个函数,第一个函数为成功时调用的函数,如果没指定将value传递下去。第二个参数为失败时调用的函数,如果没指定,将reason作为错误抛出传递下去
     Promise.prototype.then = function (onResolved,onRejected) {
     	  const self =this;
         //catch只会处理失败,所以成功的要继续向后传递成功
         onResolved =typeof onResolved==='function'?onResolved:value => value;
         //实现异常穿透
         onRejected =typeof onRejected==='function'?onRejected:reason =>{throw reason};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 返回值为promise对象A,A的状态由.then的回调函数返回值
    • 如果抛出异常,A的状态为rejected,reason为抛出的异常
    • 如果返回非promise的值,A的状态为resolved,value为返回值
    • 如果返回promise对象B,那么A的状态由B执行的结果决定
     return new Promise((resolve,reject)=>{
        //根据回调函数执行的结果修改返回的promise的状态
        function handle(callback) {
             try {
               const result = callback(self.data);//放在里面,放在外面这个会异步执行,返回获取不到,需要知道结果是什么
               if(result instanceof Promise){//情况3
               result.then(
                      value => resolve(value), //如果这个执行说明返回的promise是成功的
                      reason=> reject(reason)//如果这个执行说明返回的promise是失败的
               );//.then才知道promise是成功还是失败
     		 }else {
                  resolve(result); //情况2 
              }
               } catch (error) {                  
                  reject(error);//情况1
               }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. .then指定回调时,考虑当前的状态,如果此时状态已经改变了说明是先改变状态后指定的回调,回调函数异步调用。如果此时状态还是pending状态,说明是先指定回调后改变状态,此时需要把回调函数存起来,待状态改变之后再调用
    //如果先指定回调,需要将回调函数保存起来,回调函数执行完毕后还需要修改新promise对象的状态。这里没设置异步执行的原因是,回调的执行是在构造函数中,在构造函数中已经指定了是异步的了
     if(self.status===PENDING){
         self.callbacks.push({
              onResolved(value){
              handle(onResolved);
          },onRejected(reason){
               handle(onRejected);
          }
          });
    } 
    //如果先改变状态,后指定回调,状态已经改变了,这里需要指定异步调用
     else if(self.status===RESOLVED){
         setTimeout(() => {
              handle(onResolved);
           });
    }
    else{//如果是reject
         setTimeout(() => {
               handle(onRejected);
          });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结

     Promise.prototype.then = function (onResolved,onRejected) {
             const self =this;
            //catch只会处理失败,所以成功的要继续向后传递成功
            onResolved =typeof onResolved==='function'?onResolved:value => value;
            //实现异常穿透
            onRejected =typeof onRejected==='function'?onRejected:reason =>{throw reason};
    
             //返回一个新的Promise对象
            return new Promise((resolve,reject)=>{
            	//根据回调函数执行的结果修改返回的promise的状态
                function handle(callback) {
                    try {
                        const result = callback(self.data);//放在里面,放在外面这个会异步执行,返回获取不到,需要知道结果是什么
                        if(result instanceof Promise){//情况3
                        // result.then(
                         //    value => resolve(value), //如果这个执行说明返回的promise是成功的
                         //    reason=> reject(reason)//如果这个执行说明返回的promise是失败的
                         //    );//.then才知道promise是成功还是失败
                         result.then(resolve,reject);//简洁写法
                        }else {
                         resolve(result); //情况2 
                        }
                     } catch (error) {                  
                         reject(error);//情况1
                     }
                }
    
    		  //如果先指定回调,需要将回调函数保存起来,回调函数执行完毕后还需要修改新promise对象的状态。这里没设置异步执行的原因是,回调的执行是在构造函数中,在构造函数中已经指定了是异步的了
                 if(self.status===PENDING){
                    self.callbacks.push({
                    onResolved(value){
                        handle(onResolved);
                    },onRejected(reason){
                        handle(onRejected);
                    }
                    });
                 } 
                 //如果先改变状态,后指定回调,状态已经改变了,这里需要指定异步调用
                 else if(self.status===RESOLVED){
                    setTimeout(() => {
                        handle(onResolved);
                    });
                 }
                else{//如果是reject
                    setTimeout(() => {
                        handle(onRejected);
                     });
                }
    		}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    promise.finally(callback)

    参数是callback函数,返回值是promise对象A,A的状态由finally前面的promise决定

    无论成功和失败都会调用,并且将值和状态原封不动的传递给后面的then

    1.需要获取前面promise执行的值,通过.then
    2.需要执行callback函数,callback函数可能是一个异步函数,需要等待它执行完毕。通过Promise.resolve包装,就可以通过.then知道callback什么时候执行结束。

    Promise.prototype.finally = function(callback){
    	let P = this.constructor
    	return this.then(
    		val => P.resolve(callback()).then(()=>val),//这返回的val是前面promise的结果,不是后面callback的结果
    		reason => P.resolve(callback()).then(()=>{throw reason})
    	)
    }
    //测试代码
    new Promise((resolve, reject) => {
      setTimeout(() => resolve("result"), 2000)
    })
    .finally(() => console.log("Promise ready"))
    .then(result => console.log(result))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Promise.all/Promise.race/Promise.allSettled

    状态只能改变一次,当状态已经改变时,resolve和reject进入后立即返回,不会获取data,修改状态执行对应的回调

    • Promise.all(数组):返回一个Promise A
      • 参数中所有的promise都成功,A的状态为成功,成功的值 为参数promise返回的值 组成的数组,顺序和参数中数组一致。
      • 数组中有promise失败,则A的状态为失败,值为第一个失败的值。
    • Promise.race(数组):返回一个promise,结果由第一个完成的promise结果决定。
      • 使用场景:promise超时请求,控制xxxms后请求超时
    • Promise.allSettIed(数组):返回一个成功的promise,promise的值为数组,数组中包含每个promise执行的状态和返回值,当所有的promise状态都改变后,将数组返回。
      • 使用场景: 希望等一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。

    手写Promise.all/Promise.race/Promise.allSettled

    Promise.all()

    • 返回一个promise,promise的结果由数组中的元素执行结果决定
    • 依次取出数组中元素的执行结果,注意如果元素是值是没有.then的,所以可以Promise.resolve(元素) 来让非promise值也有.then
    • 如果成功就按promise在数组中的顺序放进结果数组中,全部成功调用resolve(结果数组)。如果失败就执行reject,表示返回的promise对象失败了
    Promise.all = function(iterator){
        let count = 0;
        let res = new Array(iterator.length);
        return new Promise((resolve,reject)=>{
        iterator.forEach((element,index) => {
          Promise.resolve(element).then(value=>{
              count++;
              res[index] = value;
              if(count == iterator.length) resolve(res);
          },reason=>{
            reject(resolve)
          })
        });
        })
    }
    
    
    const p1 = new Promise((resolve, reject) => {
      resolve('成功了')
    })
    const p2 = Promise.resolve('success')
    const p3 = Promise.reject('失败')
    
    Promise.all([p1, p2]).then((result) => {
      console.log(result)  //["成功了", "success"]
    }).catch((error) => {
      //未被调用
    })
    
    Promise.all([p1, p3, p2]).then((result) => {
      //未被调用
    }).catch((error) => {
      console.log(error)  //"失败"
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    Promise.allSettled ()

    Promise.allSettled = function(iterator){
        let res = new Array(iterator.length);
        let count = 0 ;
        return new Promise((resolve,reject)=>{
        const pushResult = (index,status,value)=>{
          res[index]= {status:status,value:value};
          count++;
          if(count == iterator.length)resolve(res);
        }
        iterator.forEach((element,index) => {
          Promise.resolve(element).then(value=>{
            fn(index,'fulfilled',value);
          
          },reason=>{
            fn(index,'rejected',reason);
          })
        });
        })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    手写题:请求五秒未完成则终止

    //提供两个模拟API
    api = () =>{};
    warning = ()=>{};
    
    function timing(){
    	return new Promise((resolve,reject)=>{
    		setTimeout(()=>{
    			reject();
    		},5000)
    	})
    }
    function apiTiming(){	
    	const arr = [api(),timing()];
    	Promise.race(arr).then(res=>{
    			console.log(res);
    	}),catch(e=>{
    		warnning(e);
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    promise实现并发的异步任务调度器

    实现一个具有并发数量限制的异步任务调度器,可以规定最大同时运行的任务。

    JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。完善下面代码的Scheduler类,使以下程序能够正常输出:

    
    class Scheduler {
      add(promiseCreator) { ... }
      // ...
    }
       
    const timeout = time => new Promise(resolve => {
      setTimeout(resolve, time);
    })
      
    const scheduler = new Scheduler(2);
      
    const addTask = (time,order) => {
    	scheduler.add(() => timeout(time).then(()=>console.log(order))) //实现1: add方法参数不是一个promise 不用管返回值
    	scheduler.add(() => timeout(time)).then(()=>console.log(order)) //实现2: add方法参数是一个promise,返回一个promise
    }
    
    addTask(1000, '1');
    addTask(500, '2');
    addTask(300, '3');
    addTask(400, '4');
    
    // output: 2 3 1 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分析
    任务调度器-控制任务的执行,当资源不足时将任务加入等待队列,当资源足够时,将等待队列中的任务取出执行
    在调度器中一般会有一个等待队列queue,存放当资源不够时等待执行的任务。

    具有并发数据限制,假设通过max设置允许同时运行的任务,还需要count表示当前正在执行的任务数量。
    当需要执行一个任务A时,先判断count==max 如果相等说明任务A不能执行,应该被阻塞,阻塞的任务放进queue中,等待任务调度器管理。
    如果count说明正在执行的任务数没有达到最大容量,那么count++执行任务A,执行完毕后count--
    此时如果queue中有值,说明之前有任务因为并发数量限制而被阻塞,现在count,任务调度器会将对头的任务弹出执行。

    // output: 2 3 1 4
    class Scheduler {
    	constructor(max) {
    		this.tasks = [], // 待运行的任务
    		this.count = 0 // 正在运行的任务
        this.max = max;
    	}
    	// promiseCreator 是一个异步函数,return Promise
    	add(promiseCreator) {//add需要返回promise
    		return new Promise((resolve, reject) => {
    			promiseCreator.resolve = resolve //将resolve保存起来,等待promiseCreator执行后,在start中调用控制add返回
    			if (this.count < this.max) {
    				this.start(promiseCreator)
    			} else {
    				this.tasks.push(promiseCreator)
    			}
    		})
    	}
    
    	start(promiseCreator) {
    		this.count++;
    		promiseCreator().then(() => {
    			promiseCreator.resolve(); //add返回
    			this.count--;
    			if (this.tasks.length) {
    				this.start(this.tasks.shift())
    			}
    		})
    	}
    }
    
    
    const timeout = (time) => new Promise(resolve => {
    	setTimeout(resolve, time);
    })
    
    const scheduler = new Scheduler(2)
    
    const addTask = (time, order) => {//() => new Promise(setTimeout(resolve, 1000))
    	scheduler.add(() => timeout(time)).then(() => console.log(order))
    }
    
    addTask(1000, '1');
    addTask(500, '2');
    addTask(300, '3');
    addTask(400, '4');
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    async和await

    class Scheduler {
      constructor(max) {
        this.tasks= []
        this.count = 0;
        this.max =max;
      }
      async add(promiseCreator) {
        if(this.count >= this.max) await new Promise(resolve => this.tasks.push(resolve)); //将resolve放入队列中等待执行
        this.count++
        const res = await promiseCreator()
        this.count--
        if(this.tasks.length) this.tasks.shift()()
        return res
      }
    }
    
    
    const timeout = (time) => new Promise(resolve => {
    	setTimeout(resolve, time);
    })
    
    const scheduler = new Scheduler(2)
    
    const addTask = (time, order) => {//() => new Promise(setTimeout(resolve, 1000))
    	scheduler.add(() => timeout(time)).then(() => console.log(order))
    }
    
    addTask(1000, '1');
    addTask(500, '2');
    addTask(300, '3');
    addTask(400, '4');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    同步任务
    1.addTask(1000, ‘1’)执行到const res = await promiseCreator()的时候,交出执行权,执行下一步
    2.addTask(500, ‘2’)同1
    3.addTask(300, ‘3’)此时count = 2 所以执行到await new Promise(resolve => this.tasks.push(resolve))的时候交出执行权,执行下一步
    4.addTask(400, ‘4’)同3

    异步任务

    1.time=500的定时器resolve, addTask(500, ‘2’)函数重获执行权,开始执行其中的后续代码:

    this.count-- //count = 1
    if(this.tasks.length) this.tasks.shift()() //使waitQueue中的第一个异步任务resolve,addTask(300, '3')重获执行权
    return res //遇到return,则async函数返回的Promise的状态resolved, 所以.then(() => console.log(time, order))输出 500 '2'
    
    • 1
    • 2
    • 3

    2.addTask(300, ‘3’)函数重获执行权,执行到const res = await promiseCreator()交出执行权
    3.time=300的定时器resolve, 后续流程同5
    4.addTask(400, ‘4’)函数重获执行权, 后续同6
    5.time=1000的定时器resolve, 后续流程同5
    6.time=400的定时器resolve, 后续流程同5

  • 相关阅读:
    Linux命令大全
    ThingsBoard 开源物联网平台
    C++PrimerPlus(第6版)中文版:Chapter13.3多态共有继承例子(重点讲虚函数):usebrass2.cpp
    golang工程——gRPC keep alive 配置
    如何解决无法npm下载electron依赖的
    Socks5代理、IP代理与网络安全:保护你的爬虫和隐私
    djangoMTV初探
    九. Linux网络命令
    LeetCode笔记:Weekly Contest 299
    [附源码]java毕业设计文档管理系统
  • 原文地址:https://blog.csdn.net/qq_41370833/article/details/126146446