本文难点:手写一个符合Promise/A+规范的Promise
一、回调函数
- 把B函数被作为参数传递到A函数里,在A函数执行完后再执行B
- 优点:回调函数是异步编程最基本的方法,简单、容易理解和部署。
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
- 缺点:不利于代码的阅读和维护,比如回调地狱;
let fs = require('fs');
fs.readFile('1.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data);
});
});
});
- 注意:注意区分同步回调和异步回调
拓展:高阶函数
1、接收一个或多个函数作为参数
//after函数
function after(times,callback){
return function(){
times--;
if(times == 0){
callback();
}
}
}
let fn = after(3,function(){
console.log("调用三次后再执行")
})
fn()
fn()
fn()
2、输出另一个函数
//检测数据类型方法
function isType(type) { // [object String]
return function (content) {
let t = Object.prototype.toString
.call(content).replace(/\[object\s|\]/g, '');
return t === type
}
}
let types = ['String','Undefined','Function','Number'];
let util = {}; // util.isString isUndefined
types.forEach(t=>{
util['is'+t] = isType(t);
});
console.log(util.isString('hello'));
二、事件的绑定、监听、委托
- 直接绑定事件-只能绑定一次
//onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等
document.getElementById("btn").onclick = function(){
console.log("hello world!");
}
- 事件监听- addEventListener() 或 attachEvent()
//可绑定多个事件
document.getElementById("btn").addEventListener("click",hello);
function hello(){
console.log("hello world!");
}
- 事件委托-事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果
var btn = document.getElementById("btn");
document.onclick = function(e){
e = e || window.event;
var target = e.target || e.srcElement;
if(target == btn){
console.log("委托成功")
}
- 注意:浏览器兼容性
三、发布订阅
- 发布订阅模式:基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知。
- 观察者模式:一个对象(称为subject)维持一系列依赖于它的对象(称为observer),将有关状态的任何变更自动通知给它们(观察者)。
//需求:读取两次文件后输出结果
let fs = require("fs");
let event = {
arr: [],
result: [],
on(fn) {
this.arr.push(fn)
},
emit(data) {
this.result.push(data);
this.arr.forEach(fn => fn(this.result)); //执行订阅的内容
}
}
event.on(function (data) { //订阅
if (data.length === 2) {
console.log(2, data); //输出
}
})
fs.readFile("1.txt", "utf-8", function (err,data) {
event.emit(data); //发布
})
fs.readFile("2.txt", "utf-8", function (err,data) {
event.emit(data); //发布
})
四、promise系列
- promise对象是commonJS工作组提出的一种规范,一种模式,目的是为了异步编程提供统一接口。 new Promise 返回一个 promise对象 接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject,promise对象初始化状态为 pending,当调用resolve(成功),会由pending => fulfilled,当调用reject(失败),会由pending => rejected
let fs = require("fs");
let promise = new Promise(function (resolve, reject) {
fs.readFile("1.txt","utf-8",function (err,data) {
if(err){
reject(err);
}
resolve(data)
})
})
promise.then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
- 优点 1、可解决回调地狱(恶魔金字塔) 2、回调函数写成了链式写法 f1().then(f2).then(f3); 3、可“同步”异步执行的结果
- 其他方法 Promise.resolve Promise.reject Promise.all Promise.race等方法适用于不同场景下的异步编程开发。
拓展:手写一个符合Promise/A+规范的Promise
- 基本实现:执行器中为异步函数,resolve时执行then中回调函数,且then的回调函数能获取到resolve(data)到结果data
- then的链式调用:then函数需要返回一个新的promise2, 才能使用点then2, 将then中回调函数A(同步或异步))放入promise2的执行器中同步执行,A函数执行完毕后调用resolve或reject,下一个then2执行时,then2中回调函数会放入事件池,当promise2中接收的参数resolve或reject执行时并传入参数data,就将本次then中回调函数的返回结果传递到了下一个then2中
- 注意:
- 只能从pending => resolve/reject
- 执行器执行异常处理-reject
- 上一个then是rejected,不影响本次then
- then中回调函数的返回值return 可能是一个常量 可能是undefined,也可能是异步promise
- 若返回值仍是promise,则一直执行直到得到一个常量值再返回
- 若then中什么都没有,值可以穿透
/**
* promise/A+规范:
* 在某些应用场景下,我们需要知道异步请求的数据在什么时候返回的,然后进行下一步处理
* 如果我们在异步操作回调里面仍是异步操作的时候,就形成了异步回调的嵌套
* 由此,Promise/a+规范应运而生。
*/
function Promise(executor) {
let self = this;
self.status = "pending"; //初始状态
self.data = undefined; //成功的值
self.err = undefined; //失败原因
// new Promise的时候可能会有异步操作,需要保存成功和失败的回调
self.onResolveCallbacks = []; //成功回调池
self.onRejectCallbacks = []; //失败回调池
//pending=>resolved
function resolve(data) {
if (self.status == "pending") {
self.status = "resolved"; //改变状态
self.data = data; //接收数据
self.onResolveCallbacks.forEach(fn => fn()); //resolve执行时-执行回调事件池(then的时候存入的)
}
}
//pending=>rejected
function reject(err) {
if (self.status == "pending") {
self.status = "rejected"; //改变状态
self.err = err; //接收错误信息
self.onRejectCallbacks.forEach(fn => fn()); //reject执行时-执行回调事件池(then的时候存入的)
}
}
try {
executor(resolve, reject); //默认new Promise时,执行器-同步执行
//但resolve reject函数没有立即执行,仅作为参数传入执行器函数,异步操作回来后再调用resolve reject
} catch (e) {
//如果执行器执行异常,直接reject-失败态
reject(e)
}
}
/**
* resolvePromise:
* 当resolve/reject的时候执行事件池->resolvePromise函数执行
*/
function resolvePromise(promise2, x, resolve, reject) {
//promise2和函数之后后返回的结果是同一个对象
if (promise2 == x) {
return reject(new TypeError("Chaining cycle"));
}
let called;
//x可能是一个普通值,也可能是一个promise
if (x != null && (typeof x == "function" || typeof x == "object")) {
//外部的promise,可能报异常
try {
let then = x.then;
// x可能还是一个promise 那么就让这个promise执行即可
// 这里的逻辑不单单是自己的 还有别人的 别人的promise 可能既会调用成功 也会调用失败
if (typeof then == "function") {
//执行promise
then.call(x, val => { //改变this指向,返回promise执行结果
if (called) return; //防止多次被调用
called = true;
//递归-执行的结果可能还是一个promise,那就循环的其解析,直到得到普通值为止
resolvePromise(promise2, val, resolve, reject)
}, err => { // promise的失败结果
if (called) return;
called = true;
reject(err)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return;
called = true;
reject(e)
}
} else {
//x 是一个常量
resolve(x)
}
}
/**
* then:
* then调用的时候 都是异步调用 (原生的then的成功或者失败是一个微任务)-用setTimeout模仿
* 成功和失败的回调 是可选参数
*/
Promise.prototype.then = function (onFulFilled, onRejected) {
//值得穿透-then里面什么都不写时
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
let self = this;
let promise2;
promise2 = new Promise((resolve, reject) => {
if (self.status == "pending") {
//本次的promise执行为异步代码-走then的时候,状态仍为pending
//执行器executor中有异步操作->then函数执行时,此时Promise的状态仍为pending
//所以then函数的主要作用之一是:将回调函数(onFulFilled和onRejected)存入事件池,等异步操作完成后通过resolve和reject执行
//then函数要返回一个新的promise,便于链式操作
self.onResolveCallbacks.push(() => { //resolve执行时,调动事件池-执行本次回调并得到结果x
setTimeout(() => {
try{
let x = onFulFilled(self.data); //本次执行结果
// resolve(x);//x若是一个常量
//本次结果x可能仍为promise-要得到最终态数据并返回
resolvePromise(promise2, x, resolve, reject);
//传参:
//本次的promise2对象、本次执行结果x,本次promise2的resolve,reject-->得到最终结果时调用
}catch(e){
// 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
reject(e);
}
}, 0);
})
self.onRejectCallbacks.push(() => {
//reject执行时,调动事件池-执行本次回调并得到结果x
setTimeout(() => {
try {
let x = onRejected(self.err);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
})
}
if (self.status == "resolved") {
//本次的promise执行为同步代码-直接resolve(data)了,并将数据传到then中的回调函数onFulFilled(self.data)
setTimeout(() => {
try {
//resolve执行后-onFulFilled再执行
let x = onFulFilled(self.data); //仍不确定执行结果x是常量还是promise,
//如果是常量则直接传给下一个promise2的resolve(x)即可,
//如果是promise,则需要继续调用promise直到得到最终结果为常量在返回给下一个promise2的resolve(x)
//下一个promise2的resolve(x)接收本次执行结果x,在本次中执行这个resolve(x),
//如此,即可将本次执行结果传递给下一个then函数的回调
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
}
if (self.status == "rejected") {
setTimeout(() => {
try {
let x = onRejected(self.err);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
}
})
return promise2
}
五、gennerator
- 先上代码
//gennerator+co
let bluebird = require("bluebird");//第三方库
let fs = require("fs");
let read = bluebird.promisify(fs.readFile);//promise化
function * gen() {//生成器
let r1 = yield read('1async/1.txt', 'utf8');
let r2 = yield read(r1, 'utf8');
let r3 = yield read(r2, 'utf8');
return r3;
}
function co(it) {
return new Promise(function (resolve,reject) {
function next(data) {
let {value,done} = it.next(data);//迭代
if (!done) {
value.then(data=>{
next(data);
},reject)
}else{
resolve(value);
}
}
next();
})
}
co(gen()).then(data=>{
console.log(data)
})
- gen和普通函数的区别在于,不是直接执行的,生成器返回的是一个gen对象,我们要通过这个对象来执行里面的逻辑
- gen的中常常用yield的关键字隔开,gen中如有有yield关键字,那么,gen中的代码不会一次被执行完成,而是会根据yield关键字,一段一段的执行完成
- gen的每次执行返回的都会返回一个next对象,value的的数值就是yield代码执行后返回的数值,done代表之后,是否还有要执行的逻辑,false代表还有,反之则无
六、async+aweit
- 优雅的代码同步写法
- 可以try + catch
- 可以使用promise的形式
- async+aweit = gennerator+co
let bluebird = require("bluebird");//第三方库
let fs = require("fs");
let read = bluebird.promisify(fs.readFile);//promise化
async function gen() {
try{
let r1 = await read('1async/1.txt', 'utf8');
let r2 = await read(r1, 'utf8');
let r3 = await read(r2, 'utf8');
return r3;
}catch(e){
throw(e)
}
}
gen().then(data=>{
console.log(data)
},err=>{
console.log(err)
})
结语
欢迎批评指正!