博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JS异步编程的几种方式
阅读量:6787 次
发布时间:2019-06-26

本文共 11518 字,大约阅读时间需要 38 分钟。

本文难点:手写一个符合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)    })复制代码

结语

欢迎批评指正!

转载于:https://juejin.im/post/5b6525e4f265da0f4a4e911c

你可能感兴趣的文章
【Linux 驱动】设备驱动程序再理解
查看>>
加密解密的概念以及DES加密算法的实现
查看>>
yum 出现错误
查看>>
Nagios(十)—— 监控路由器
查看>>
禁止ping主机
查看>>
基于heartbeat v2 crm实现基于nfs的mysql高可用集群
查看>>
TensorFlow学习笔记-TensorBoard启动
查看>>
lduan SCO 2012 集成式部署(一)
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
基础排序算法 – 插入排序Insertion sort
查看>>
Spring mvc ViewResolver视图解析器实现机制
查看>>
源码安装Apache 2.4.18
查看>>
spring--(6)p标签的使用
查看>>
java --泛型
查看>>
怎样将PPT文件转换为Word文档精美ppt模板下载
查看>>
ARM编辑、编译工具
查看>>
数字签名
查看>>
centos7安装docker镜像源管理工具harbor
查看>>