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

本文详细介绍了异步编程的几种解决方案,包括Generator函数、Iterator接口、async/await和Promise。Generator作为状态机,允许暂停和恢复执行;Iterator提供统一的遍历接口;async/await是基于Promise的语法糖,简化了异步处理。文章还深入探讨了它们的工作原理、使用场景和面试题,帮助读者深入理解这些技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

异步编程解决方案 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对象

原理
function*生成器函数进行转换,转换后的代码分成三大块。

  • context对象:存储函数执行上下文,Generator实现的核心在于上下文的保存,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样。
  • gen$(_context)根据yieId语句将代码分割成switch-case块,后续通过切换_context.prev_context.next来分别执行各个case,走到下一步
  • invoke()方法定义next(),用于执行gen$(_context)来跳到下一步
// 低配版context  
var context = {
  next:0,
  prev: 0,//
  done: false, //是否执行执行完毕
  stop: function stop () {
    this.done = true
  }
}
// 生成器函数根据yield语句将代码分割为switch-case块,后续通过切换_context.prev和_context.next来分别执行各个case
function gen$(_context) {
  while (1) {
    switch (_context.prev = _context.next) {
      case 0:
        _context.next = 2;
        return 'result1';

      case 2:
        _context.next = 4;
        return 'result2';

      case 4:
        _context.next = 6;
        return 'result3';

      case 6:
      case "end":
        return _context.stop();
    }
  }
}
// 低配版invoke
let gen = function() {
  return {
    next: function() {
      value = context.done ? undefined: gen$(context)
      done = context.done
      return {
        value,
        done
      }
    }
  }
} 

作者:写代码像蔡徐抻
链接:https://juejin.cn/post/6844904096525189128
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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. 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 { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }
基于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);

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. 可迭代的数据内部都有[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. 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()); //开始执行迭代器
  });
}  

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()
  • 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. 返回值为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. .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);
      });
}

总结

 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);
                 });
            }
		}
}

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))

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(promises){
    let count = 0;
    let res = new Array(promises.length);
    return new Promise((resolve,reject)=>{
    promises.forEach((promise,index) => {
      Promise.resolve(promise).then(value=>{
          count++;
          res[index] = value;
          if(count == promises.length) resolve(res);
      },reason=>{
        reject(reason)
      })
    });
    })
}

const p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})
const p2 = new Promise((resolve, reject) => {
 setTimeout(() => {
     resolve('success')
 }, 2000);
})
const p3 =new Promise((resolve, reject) => {
 setTimeout(() => {
      reject('失败')
  }, 1000);
}) 
Promise.all([p1, p2]).then((result) => {
  console.log(result)  //["成功了", "success"]
}).catch((error) => {
  //未被调用
})

Promise.all([p1, p3, p2]).then((result) => {
  //未被调用
}).catch((error) => {
  console.log(error)  //"失败"
});

Promise.allSettled ()

Promise.allSettled([p1, p2,p3]).then((result) => {
    console.log(result)  
    /*
    [
        { status: 'fulfilled', value: '成功了' },
        { status: 'fulfilled', value: 'success' },
        { status: 'rejected', reason: '失败' }
    ]
    */
  }).catch((error) => {
    console.log(error)
})
  


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);
      })
    });
    })
}

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

//提供两个模拟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);
	})
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值