IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> js 高阶函数 发布订阅观察者模式 手撕promise -> 正文阅读

[JavaScript知识库]js 高阶函数 发布订阅观察者模式 手撕promise

promise

高阶函数

  • 概念:1 一个函数返回一个函数。2 函数参数可以接受一个函数。

满足任意两点即可。

  • 场景: 扩展方法
function core(...args){

    // 核心代码
    console.log('core');

    
}

要在core核心代码执行前后处理一些逻辑,但是不能修改Core的代码,怎么操作呢?

function core(...args) {
  // 核心代码
  console.log("core", ...args);
}

Function.prototype.before = function (cb) {
  return (...args) => {
    cb(); //cb就是要处理的逻辑
    this(...args);
  };
};

const newCode = core.before(() => {
  console.log("我是先处理的");
});

//返回新的core函数,不需要知道里面做什么操作,只需要像调用core一样调用就行。
newCode(1, 2, 3);

通过高阶函数,使用回调函数,让需要处理的操作先执行,在执行core函数。结果如图:
在这里插入图片描述

函数柯里化

n参数的传入,把他转为n个函数。如

const fn = (a,b,c)=>{return a+b+c}
fn(1,2,3)
//柯里化,可以暂存变量。
const fn = (a,b) => {
	//这里会利用闭包存放a,b的变量
	return (c) => {
		return a+b+c
}
}
fn(1,2)(3)

判断类型

function isType(type) {
  return (val) => {
    return Object.prototype.toString.call(val) === `[object ${type}]`;
  };
}

const utils = {};

[
  "String",
  "Number",
  "Object",
  "Gunction",
  "Null",
  "Nndefined",
  "Boolean",
].forEach((item) => {
  utils[`is${item}`] = isType(item);
});

console.log(utils.isString(123)); //fase
console.log(utils.isString("123"));

采用柯里化保存每个类型。

并发问题

比如同时读取两个文件的内容,读取完后显示出来。

const fs = require("fs");

//多个请求并发,靠计数器实现
function after(times, callback) {
  //times控制数量
  let arr = []; //记录结果
  return (data, index) => {
    arr[index] = data;
    if (--times === 0) {
      callback(arr);
    }
  };
}
const out = after(2, (data) => {
  console.log(data);
});

fs.readFile("./1.txt", "utf-8", (err, data) => {
  out(data,1);
});

fs.readFile("./2.txt", "utf-8", (err, data) => {
  out(data,0);
});

这里也是借助高阶函数的概念,拿到值之后存放起来。这种方法不是很好。
再次优化,发布订阅模式

//事件中心
const fs = require("fs");

// 解耦合,将每个逻辑写到了各自的类里面
const events = {
  arr: [],
  on(cb) {
    events.arr.push(cb);
  },
  emit(data) {
    events.arr.forEach((item) => item(data));
  },
};

events.on(() => {
  console.log("每次发布就打印一次");
});

const arr = [];
events.on((data) => {
  arr.push(data);
});
events.on(() => {
  if (arr.length === 2) {
    console.log(arr);
  }
});

fs.readFile("./1.txt", "utf-8", (err, data) => {
  events.emit(data)
});

fs.readFile("./2.txt", "utf-8", (err, data) => {
  events.emit(data)
});

观察者模式vue2 基于发布订阅

发布订阅之间是没有依赖关系的,而观察者模式是有关系的。
vue2中的Updater类,在每个数据调用get方法的时候,会订阅watcher,将wathcer放入updater中。然后set方法的时候通知updater,Updater类就会调用每个watcher的updater方法通知数据改变了。

class Subsject {
  //被观察者,需要将观察者收集起来。改变的时候通知观察者。
  constructor(name) {
    this.name = name;
    this.observers = [];
    this.state = "正常";
  }
  attach(o) {
    this.observers.push(o);
  }
  setState(state) {
    this.state = state;
    //通知观察者
    this.observers.forEach((item) => item.update(this.state));

  }
}

class Observer {
  //观察者
  constructor(name) {
    this.name = name;
  }

  update(state){
    console.log(`我是${this.name},宝宝现在${state}`);
  }
}

// vue数据改变了, 需要通知依赖的视图

//被观察者
let s = new Subsject("小宝宝");

//观察者
let o1 = new Observer("爸爸");
let o2 = new Observer("妈妈");

//模范get方法时手机依赖
s.attach(o1);
s.attach(o2);

//宝宝状态改变,模仿set方法
s.setState("不开心了");

使用宝宝模拟被观察者,使用父母模拟观察者。当被观察者状态改变的时候,就会通知,调用观察者的update方法。

promise

使用promise的时候会传入一个执行器,会立即执行。
promise有三个状态,pending,fullied,reject。默认是pending。

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = PENDING; //状态
    this.value = undefined; //成功
    this.reason = undefined; //失败原因
    const resolve = (value) => {
      //成功resolve函数
      if (this.status !== PENDING) {
        return;
      }
      this.value = value
      this.status = FULFILLED;
    };
    const reject = (reason) => {
      //失败函数
      if (this.status !== PENDING) {
        return;
      }
      this.reason = reason
      this.status = REJECTEDD;
    };
    //执行器有可能会跑错。
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === PENDING) {
    } else if (this.status === FULFILLED) {
      onFulfilled(this.value);
    } else {
      onRejected(this.reason);
    }
  }

  catch(onRejected) {
    onRejected(this.reason)
  }
}

const promise = new Promise((resolve, reject) => {
  console.log("promise");
  resolve(1);
  reject(2);
});

promise.then(
  (data) => {
    console.log(data);
  },
  (err) => {
    console.log(123123);
    console.log(err);
  }
);


简单的promise的实现,主要实现三个状态的改变以及执行器执行的时候也可能会抛出错误,所以需要try catch一下。这样简单的同步promise就实现了。
接着继续实现异步的。

const promise = new Promise((resolve, reject) => {
  console.log("promise");
  setTimeout(() => {
    resolve(2);
  }, 2000);
});

实现思路:
当我们调用then的时候resolve还没执行,此时status还是Pending,所以我们需要把当前的回调函数暂存起来,当resolve执行的时候需要取出来去执行。

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise{
  constructor(executor) {
    this.status = PENDING; //状态
    this.value = undefined; //成功
    this.reason = undefined; //失败原因
    this.thenArr = []
    this.rejectArr = []
    const resolve = (value) => {
      //成功resolve函数
      if (this.status !== PENDING) {
        return;
      }
      this.value = value;
      this.status = FULFILLED;
      if(this.thenArr.length){
        this.thenArr.forEach(item=>item(value))
      }
    };
    const reject = (reason) => {
      //失败函数
      if (this.status !== PENDING) {
        return;
      }
      this.reason = reason;
      this.status = REJECTED;
      if(this.rejectArr.length){
        this.rejectArr.forEach(item=>item(reason))
      }
    };
    //执行器有可能会跑错。
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === PENDING) {
      this.thenArr.push(onFulfilled)
      this.rejectArr.push(onRejected)
    } else if (this.status === FULFILLED) {
      onFulfilled(this.value);
    } else {
      onRejected(this.reason);
    }
  }

  catch(onRejected) {
    if(this.status === PENDING){
      this.rejectArr.push(onRejected)
    } else if(this.status === REJECTED){
      onRejected(this.reason);
    }
  }
}

const promise = new Promise((resolve, reject) => {
  console.log("promise");
  setTimeout(()=>{
    resolve(2)
  },2000)
});

promise.then(
  (data) => {
    console.log('data1', data);
  },
  (err) => {
    console.log("err", err);
  }
);
promise.catch(err=>{
  console.log('catch',err);
})
promise.then(
  (data) => {
    console.log('data2', data);
  },
  (err) => {
    console.log("err", err);
  }
);
promise.then(
  (data) => {
    console.log('data2', data);
  },
  (err) => {
    console.log("err", err);
  }
);



在这里插入图片描述
在这里插入图片描述
通过发布订阅的模式就可以实现。

promise的特点

解决了 链式调用解决回调地狱 和 同步并发 的问题。

  • 链式调用
    情况1: then中返回一个普通值(不是promise)的情况,会作为外层下一次then的成功结果。每次then返回的是新的promise。
promise.then(
  (data) => {
    console.log("data1", data);
    throw new Error(333)
  },
  (err) => {
    console.log("err", err);
  }
).then(data2=>{
  console.log('data2', data2);
}, err2=>{
  console.log('err2', err2);
});

第一次then中的返回值会作为第二次then的结果,抛错会被第二层catch捕获。
情况2: then中方法抛错,会作为下一次then的失败结果。(如果下一次then没有捕获,会继续往下走)
无论上一次then走的是成功还是失败,只要返回普通值,都会执行下一次then的成功。
情况3: then中返回新的promise,成功则走成功,失败或者报错就被捕获。
实现:

  • 思路:就是then方法和catch方法每次返回一个新的promise,然后因为我们的.then是微任务,所以我们使用setTimeout将then要执行的函数挂起来,让同步任务先走。这里不是queueMicrotask模拟微任务是因为它本来就是Promise实现的。
  • 接着,我们需要判断返回的值x是什么类型,如果是普通类型直接调用resolve即可,如果是Promise就会判断promise的状态的。
  • 异步执行也是一样的道理,将setTiemout包裹起来放进去数组里面,等待resolve执行去调用,才会继续走下去。
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    //如果函数执行返回的是自己的promise2,那么就死循环
    throw new TypeError("不能引用自己的promise");
  }

  //兼容别人写的promise
  if ((x && typeof x === "object") || typeof x === "function") {
    //如果x是promise或者对象
    try {
      const then = x.then;
      if (typeof then === "function") {
        //是promise了,执行then函数,传入两个函数,resolve,和reject。
        //这里相当于调用new Promise().then(res=>{resolve(res)}, err=>{reject(err)}),传入了我们定义的两个函数,然后
        //调用then的时候,他们会被执行,失败就失败,成功就成功。
        // x是promise,相当于x.then(res=>{resolve(res)},err=>{reject(err)})
        then.call(
          x,
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        //就是一个对象,里面有then属性
        resolve(x);
      }
    } catch (e) {
      reject(e);
    }
  } else {
    //返回的x是普通纸,直接resolve即可
    resolve(x);
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING; //状态
    this.value = undefined; //成功
    this.reason = undefined; //失败原因
    this.thenArr = [];
    this.rejectArr = [];

    const resolve = (value) => {
      //成功resolve函数
      if (this.status !== PENDING) {
        return;
      }
      this.value = value;
      this.status = FULFILLED;
      if (this.thenArr.length) {
        this.thenArr.forEach((item) => {
          item(value);
        });
        this.thenArr = [];
      }
    };

    const reject = (reason) => {
      //失败函数
      if (this.status !== PENDING) {
        return;
      }
      this.reason = reason;
      this.status = REJECTED;
      if (this.rejectArr.length) {
        this.rejectArr.forEach((item) => {
          item(reason);
        });
        this.rejectArr = [];
      }
    };
    //执行器有可能会跑错。
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilled, onRejected) {
    //返回全新的Promise
    const promise2 = new Promise((resolve, reject) => {
      if (this.status === PENDING) {
        //改造onFuil
        this.thenArr.push(() => {
          setTimeout(() => {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          });
        });
        this.rejectArr.push(() => {
          setTimeout(() => {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          }, 0);
        });
      } else if (this.status === FULFILLED) {
        //必须通过setTimoeut才能拿到promise2。不然同步的话promise还没执行完无法传给resolvePromise
        setTimeout(() => {
          const x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        }, 0);
      } else {
        setTimeout(() => {
          const x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        }, 0);
      }
    });
    return promise2;
  }

  catch(onRejected) {
    return new Promise((resolve, reject) => {
      if (this.status === PENDING) {
        this.rejectArr.push(onRejected);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          const x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        }, 0);
      }
    });
  }
}

const promise = new Promise((resolve, reject) => {
  console.log("1");
  setTimeout(() => {
    reject(2);
  }, 2000);
  console.log("3");
});

promise
  .then(
    (data) => {
      console.log("data1", data);
    },
    (err) => {
      console.log("err1", err);
      return new Promise((re, rj) => {
        re(2);
      });
    }
  )
  .then(
    (data2) => {
      console.log("data2", data2);
    },
    (err2) => {
      console.log("err2", err2);
    }
  )
  .then((data3) => {
    console.log("data3", data3);
  });
console.log(4);

关键就是:
在这里插入图片描述
这里的then返回新的promise,对于异步的,需要将其包裹成一个函数存放起来,当resolve执行的时候再去拿出来执行,再去执行真正的then函数。
而同步的话就是直接放入setTimeout,模拟微任务。获取then函数返回的值x。调用resolvePromise函数。
在这里插入图片描述
这里对返回的x做判断,如果是prmise就做对应的处理。
这样我们的promise就完成了。
但是,如果返回的promise的resolve又是promise呢?
在这里插入图片描述
在这里插入图片描述
这里对res的处理还必须包一层。
而且then的两个参数是可选的,也要做处理。
在这里插入图片描述
原生的实现,如果没有被处理,会一直往外传。
原生promise的then,如果onFuillied或者onReject如果不是函数,会被忽略。
思路:处理很简单,如果不是函数,我们就自己写一个函数,将值传递下去就行了,只需要注意reject是需要抛出错误的,而不是传递,传递的话会被下一个resolve捕获。抛出错误才会被try catch捕获交给reject。
在这里插入图片描述
改写即可。在这里插入图片描述
在这里插入图片描述
会一层一层跑下来。

总结

promise可以是一个函数,或者一个类,他接受一个执行器。传入对应的参数。有三个状态,执行resolve的时候会将状态改为fuilled,执行reject的时候会将状态改为reject。顺便将值存起来。当同步的时候,调用.then或者.catch的时候,再将值作为入参传给函数。
异步的时候,调用.then的时候状态还未改变,需要将then的参数存放起来,等异步执行完毕调用resolve或者reject的时候再执行。
因为promise可以链式调用。所以.then必须返沪一个新的promise。为了模拟微任务,需要将then函数的内容放入setTimeout去执行。不会阻塞同步代码。
这样我们的promise就基本实现了。接着需要实现他的静态方法。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-02 16:39:54  更:2021-12-02 16:40:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:55:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码