JS知识点回顾——异步解决方案

90 阅读8分钟

异步和同步

同步

执行某个任务时,没有得到结果之前不会继续后续操作

异步

一个异步任务执行之后,没有得到结果之前就可以继续执行后续操作,异步任务完成之后,一般通过回调通知调用者;一般异步任务都会开启另一个线程

常见的异步任务:setTimeout、fetch、XMLHttpRequest

异步的解决方案

回调函数

使用setTimeout模拟请求

function login(callback) {
  setTimeout(() => {
    callback("token")
  }, 100);
}
function getOrderId(token,callback) {
  if(token){
    setTimeout(() => {
      callback("orderId")
    }, 100);
  }
}
function orderDetails(orderId,callback) {
  if(orderId){
    setTimeout(() => {
      callback("订单详情")
    }, 100);
  }
}
login((token)=>{
    getOrderId(token,(orderId)=>{
        orderDetails(orderId,(orderInfo)=>{
            console.log(orderInfo)
        })
    })
})

缺点:

1、形成回调地狱

2、高度耦合

3、不容易维护

4、不能直接使用return

事件驱动

基于回调函数,思想是异步任务的执行取决于事件的驱动

// 登录完成派发事件
const loginEvent = new CustomEvent("login-end",{detail:"token"})
function login() {
  setTimeout(() => {
    window.dispatchEvent(loginEvent)
  }, 100);
}
// 监听登录完成,执行获取订单id事件,完成订单id获取之后又派发事件
const orderEvent = new CustomEvent("order-end",{detail:"orderId"})
function getOrderId(token) {
  if(token){
    setTimeout(() => {
      window.dispatchEvent(orderEvent)
    }, 100);
  }
}
function tokenListener(ev) {
  getOrderId(ev.detail)
}
window.addEventListener("login-end",tokenListener)
const orderDetailsEvent = new CustomEvent("orderDetails-end",{detail:"订单详情"})
function orderDetails(orderId) {
  if(orderId){
    setTimeout(() => {
      window.dispatchEvent(orderDetailsEvent)
    }, 100);
  }
}
function orderListener(ev) {
  orderDetails(ev.detail)
}
window.addEventListener("order-end",orderListener)
window.addEventListener("orderDetails-end",(e)=>{
  console.log(e.detail)
})
login()

优点:

去耦合、便于实现模块化

缺点:

运行流程不清晰、阅读代码困难

发布订阅

也是使用事件驱动,但是有一个消息中心,可以看到消息流转

订阅中心

class MsgCenter {
  constructor(){
    this.listeners = {};
  }
  // 消息订阅
  on(type,listener){
    if(this.listeners[type] === undefined){
      this.listeners[type] = [];
    }
    this.listeners[type].push(listener);
    return listener;
  }
  // 发送
  dispatch(type,args = {}){
    if(!type){
      throw new Error("Event object missing 'type' property")
    }
    if(this.listeners[type] instanceof Array){
      const listeners = this.listeners[type];
      for(let i=0;i<listeners.length;i++){
        listeners[i].call(this,type,args)
      }
    }
  }
  // 取消订阅
  off(type, listener){
    if (this.listeners[type] instanceof Array) {
      const listeners = this.listeners[type];
      for(let i=0;i<listeners.length;i++){
        if(listeners[i] === listener){
          listeners[i].splice(i,1);
          break;
        }
      }
    }
  }
}

消息订阅

const myMsgCenter = new MsgCenter();
function login() {
  setTimeout(() => {
    myMsgCenter.dispatch("login-end",{detail:"token"})
  }, 100);
}
function getOrderId(token) {
  if(token){
    setTimeout(() => {
      myMsgCenter.dispatch("order-end",{detail:"orderId"})
    }, 100);
  }
}
function tokenListener(type,ev) {
  getOrderId(ev.detail);
  myMsgCenter.off(type,tokenListener)
}
myMsgCenter.on("login-end",tokenListener)
function orderDetails(orderId) {
  if(orderId){
    setTimeout(() => {
      myMsgCenter.dispatch("orderDetails-end",{detail:"订单详情"})
    }, 100);
  }
}
function orderListener(type,ev) {
  orderDetails(ev.detail)
  myMsgCenter.off(type,orderListener)
}
myMsgCenter.on("order-end",orderListener)
myMsgCenter.on("orderDetails-end",(e)=>{
  console.log(e.detail)
})
login()

Promise

特点:拉平回调函数,把回调嵌套变为链式调用;本质其实还是回调

function login() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("token")
    }, 100);
  })
}
function getOrderId(token) {
  return new Promise((resolve, reject) => {
    if(token){
      setTimeout(() => {
        resolve("orderId")
      }, 100);
    }
  })
}
function orderDetails(orderId) {
  return new Promise((resolve, reject) => {
    if(orderId){
      setTimeout(() => {
        resolve("订单详情")
      }, 100);
    }
  })
}
login().ten(getOrderId).then(orderDetails).then(r=>console.log(r))

缺点:

代码不够简洁,每个方法都需要Promise包裹

无法取消Promise

错误需要通过回调再去捕获,会麻烦一点

Generator

特点就是可以控制函数执行;将Promise的链式调用封装一下

function* execute() {
  const token = yield login()
  const orderId = yield getOrderId(token)
  const details = yield orderDetails(orderId)
}
let g = execute();
let {value,done} = g.next();
value.then((token)=>{
  let {value,done} = g.next(token);
  value.then((orderId)=>{
    let {value,done} = g.next(orderId);
    value.then((r)=>{
        console.log(r)
    })
  })
})

async + await

async函数是Generator函数的语法糖;目前算是最优的一种方法

async function execute() {
  const token = await login()
  const orderId = await getOrderId(token)
  const details = await orderDetails(orderId)
  console.log(details)
}
execute()

Promise

Promise必定会有结果并且只可能有两种结果:

pendding(初始状态)——>fulfilled(成功状态)

pendding——>rejected(失败状态)

静态方法

resolve:成功状态返回的Promise对象

reject:失败状态返回的Promise对象

all:执行多个Promise对象,全部执行成功或者任意一个失败返回的Promise对象

allSettled:执行多个Promise对象,不论成功失败,结果全部返回

any:接受一个Promise集合,返回第一个成功者

race:执行多个Promise,返回最快的Promise触发结果,不管成功还是失败;race等待一个Promise执行之后,其他并不会停止,只是race有结果了

const p1 = new Promise((resolve,reject)=>{
  setTimeout(() => {
    console.log('p1')
    resolve(1)
  }, 3000);
});
const p2 = new Promise((resolve,reject)=>{
  setTimeout(() => {
    console.log('p2')
    resolve(2)
  }, 2000);
});
const p3 = new Promise((resolve,reject)=>{
  setTimeout(() => {
    console.log('p3')
    resolve(3)
  }, 1000);
});
Promise.race([p1, p2, p3]).then((r)=>{
    console.log(r)
})

原型方法

prototype.then:返回一个Promise,最多两个参数,成功和失败后的回调函数

prototype.catch:返回一个Promise,处理失败的情况

prototype.finally:返回一个Promise,在Promise结束时,无论是成功或者失败都会执行

多次尝试

function isFunction(fn) {
      return typeof fn === "function" || fn instanceof Function;
    }
// fn要执行的函数、count执行次数、assert断言函数
function retry(fn,count,assert=()=>false) {
  if(!isFunction(fn)){
    return Promise.resolve(fn);
  }
  return new Promise(async (resolve, reject) => {
    let err = null;
    for(let tryCount = 1;tryCount <= count;tryCount++){
        try{
          const res = await fn(tryCount);
          if(assert(res,tryCount)){
            return resolve(res)
          }
        }catch(e){
          err = e;
        }
    }
    reject("多次尝试失败")
  })
}
let index = 0;
function createPromise(tryCount) {
  return new Promise((resolve, reject)=>{
      index++;
      setTimeout(() => { resolve(index)}, 1000);
  })
}
retry(createPromise,10,(res)=>{
  return res == 5
}).then(r=>console.log(r))

异常捕获

1、.catch

Promise.resolve("success").then((res)=>{
        throw new Error("error")
}).catch((err)=>{console.log(err)});

2、监听unhandledrejection事件;异步好像是监听不到的

const p = new Promise((resolve)=>{
      throw new Error("error")
      resolve(1)
});
window.addEventListener('unhandledrejection', (e)=>{
        console.log(e)
})

监听不到

const p = new Promise((resolve)=>{
      setTimeout(() => {
        throw new Error("error")
        resolve(1)
      }, 100);
});
window.addEventListener('unhandledrejection', (e)=>{
        console.log(e)
})

3、reject影响链式调用;注意catch的执行条件,只reject了一次,所以之后执行一次

const p = new Promise((resolve,reject)=>{       reject(1) 
}); 
p.then((success)=>{ 
    // 不会执行 
    console.log(success) 
},(err)=>{ 
  console.log(err) 
  return err; 
}).then((res)=>{ 
    console.log(res) 
}).catch((err)=>{ 
    // 不会执行 
    console.log(err) 
})

resolve或者reject之后代码还是会继续执行,但是resolve之后再抛出异常是不能被reject捕获的,反之也是

值穿透

const p = new Promise((resolve,reject)=>{       resolve(1) 
}); 
p.then(2).then((r)=>{ 
        console.log(r) // 1 
})

手写Promise

class MyPromise {
  constructor(callback){
    this.PromiseStatus = 'pending';
    this.PromiseValue = null;
    // 初始化callback集合
    this.initCollections();
    // 绑定this指向
    this.initBind();
    this.executor(callback);
  }
  initCollections(){
    this.fulfilledCallbacks = [];
    this.rejectedCallbacks = [];
  }
  initBind(){
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
  }
  // 如果抛出异常,会执行reject回调
  executor(callback){
    try {
      callback(this.resolve,this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  resolve(value){
    if(this.PromiseStatus !== 'pending'){
      return;
    }
    this.PromiseStatus = 'fulfilled';
    this.PromiseValue = value;
    while (this.fulfilledCallbacks.length) {
      this.fulfilledCallbacks.shift()(this.PromiseValue);
    }
  }
  reject(value){
    if(this.PromiseStatus !== 'pending'){
      return;
    }
    this.PromiseStatus = 'rejected';
    this.PromiseValue = value;
    while (this.rejectedCallbacks.length) {
      this.rejectedCallbacks.shift()(this.PromiseValue);
    }
  }
  then(onFulfilled, onRejected){
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : ()=>{};
    onRejected = typeof onRejected === 'function' ? onRejected : ()=>{};
    const nextPromise = new MyPromise((resolve, reject)=>{
    const handlePromise = (fn)=>{
      setTimeout(() => {
        try {
          // 执行回调,取得返回的内容
          const res = fn(this.PromiseValue);
          if(res === nextPromise){
            throw new Error("不能返回自身")
          }
          if(res instanceof MyPromise){
            res.then(resolve,reject)
          }else {
            resolve(res);
          }
        } catch (error) {
          reject(error)
        }
      });
      }
      if(this.PromiseStatus === 'fulfilled'){
        handlePromise(onFulfilled);
      }else if(this.PromiseStatus === 'rejected'){
        handlePromise(onRejected);
      }else {
        this.fulfilledCallbacks.push(handlePromise.bind(this,onFulfilled))
        this.rejectedCallbacks.push(handlePromise.bind(this,onRejected))
      }
    })
    return nextPromise;
  }
}
const p = new MyPromise((resolve,reject)=>{
  resolve(10)
})
p.then((r)=>{
    return r* 10
}).then((r)=>{
    console.log(r)
})
console.log(2)

async+await

generator函数(生成器函数)

1、本质是一个普通函数,function关键字和函数名之间有一个星号

function* generator1() {} // 推荐写法
function * generator1() {}
function *generator1() {}
function*generator1() {}

2、里面可以使用yield表达式,遇到yield会暂停执行,等待外面调用next返回对应状态;知道遇到return或者后面不再有执行语句

function* generator1() {
  yield 1;
  yield 2;
  yield 3;
}
const g = generator1();
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

1690437998036.png

动态创建generator函数
const GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor;
const generator1 = new GeneratorFunction(`
  yield 1;
  yield 2;
  yield 3;
`)
const g = generator1();
console.log(g.next())
console.log(g.next())

1690438516983.jpg

判断是否是generator函数
const GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor;
function* generator1() {
  yield 1
}
function fn() {
  console.log(1)
}
console.log(generator1.constructor === GeneratorFunction) // true
console.log(fn.constructor === GeneratorFunction) // false
符合可迭代协议和迭代器协议
function* generator() {
  yield 1;
  yield 2;
  yield 3;
}
const g = generator();
for(let v of g){
  console.log(v)
}
console.log(g.next());

image.png

传递参数

生成器函数本身可以接收参数;调用next函数的时候也可以额外传递参数

next函数的返回值是生成器传递出去的参数

function* generator(num1) {
  let num2 = 0;
  while (true) {
    num2 = yield num1 + num2;
  }
}
const g = generator(10);
console.log(g.next()); // 10
console.log(g.next(20)); // 30
console.log(g.next(30)); // 60

generator对象(迭代器对象)

generator函数执行返回的对象一般称为generator对象,JS中的对象模型也叫Generator

对象上的方法:

next() 获取下一个状态

return() 给定的值并结束生成器

function* generator1() {
  yield 1;
  yield 2;
  yield 3;
}
const g = generator1();
console.log(g.next())
console.log(g.return(10))
console.log(g.next())

1690438201272.jpg

throw() 向生成器抛出异常,并恢复生成器的执行,返回带有done和value两个属性的对象;如果generator不捕获异常,会导致调用出错;捕获的时候也有返回值

function* generator() {
  let val = 1;
  while (true) {
    try {
      yield val++;
    } catch (error) {
      console.log('error:'+error)
    }
  }
}
const g = generator();
console.log(g.next());
console.log(g.throw(new Error('error')));
console.log(g.next());

1690440438520.jpg

不捕获会直接报错

function* generator() {
  let val = 1;
  while (true) {
    yield val++;
  }
}
const g = generator();
console.log(g.next());
console.log(g.throw(new Error('error')));

1690440438520.jpg

迭代器协议

next():

是一个函数,返回的对象中包含两个属性

done:如果迭代器可以产生序列中的下一个值为false,否则为true

value:迭代器返回的任意值

可迭代器协议:允许JS对象定义或者制度它们的迭代行为,比如用for of遍历

让普通对象成为可迭代对象:设置[Symbol.iterator]属性,值为一个无参数的函数,返回值是一个符合迭代器协议的对象

function makeRangeIterator(start = 0, end = Infinity, step = 1) {
    let nextIndex = start;
    let iterationCount = 0;

    const rangeIterator = {
      next: function () {
        let result;
        if (nextIndex < end) {
          result = { value: nextIndex, done: false };
          nextIndex += step;
          iterationCount++;
          return result;
        }
        return { value: iterationCount, done: true };
      },
    };
    return rangeIterator;
}
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
    console.log(result.value); // 1 3 5 7 9
    result = it.next();
}

序列生成器

function* sequenceGenerator(start = 0,step = 1,end = Number.MAX_SAFE_INTEGER) {
  let current = start;
  while (current < end) {
    yield current
    current += step;
  }
}
const g = sequenceGenerator(0,3,20);
console.log(g.next()); // 0
console.log(g.next()); // 3
console.log(g.next()); // 6

const g2 = sequenceGenerator(0,3,20);
console.log([...g2]) //  [0, 3, 6, 9, 12, 15, 18]

状态机

function* stateMachineGenerator(states,state) {
  const len = states.length;
  let index = Math.max(states.findIndex(s => s === state), 0);
  while (true) {
    yield states[(++index) % len];
  }
}

async函数

使用async关键字声明的函数,async函数的构造函数是AsyncFunction,允许在其中使用await关键,是调用Promise的一种简洁方式

如果await的不是一个Promise实例,那么会被包裹成一个Promise实例

async function test() {
  const r1 = await 1;
  const r2 = await 2;
  console.log(r1 + r2);
}
test();

将test方法转换成es5

const _awaiter = function (thisArg,_arguments,p,generator) {
  function adopt(value) {
    return value instanceof Promise ? value : new Promise(function (resolve) { resolve(value)});
  }
  return new Promise(function(resolve,reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (error) {
        reject(error)
      }
    }
    function rejected(value) {
      try {
        step(generator.throw(value));
      } catch (error) {
        reject(error)
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled,rejected)
    }
    generator = generator.apply(thisArg,_arguments || []);
    step(generator.next())
  })
}
function test() {
  return _awaiter(this,void 0,void 0,function* () {
    const r1 = yield 1;
    const r2 = yield 2;
    console.log(r1 + r2);
  })
}
test()