本文难点:手写一个符合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) })复制代码
结语
欢迎批评指正!