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就基本实现了。接着需要实现他的静态方法。
|