callback

作者: roastwind | 来源:发表于2019-10-15 12:21 被阅读0次

    callback

    前言

    setInterval: 另类的callback实现

    • setInterval同级别的另外一个函数:setTimeout。
    • 设置n秒后,有一定时间延时的,2ms左右;
    • 最低时间为4ms,参考传送门
    var d = new Date, count = 0, f, timer;
    timer = setInterval(f = function (){
        if(new Date - d > 1000) {
            clearInterval(timer), console.log(count);
        }
        count++;
    }, 0);
    
    • setTimeout中的错误使用try,catch不可捕获
    try{
        setTimeout(function(){
            throw new Error("我不希望这个错误出现!")
        }, 1000);
    } catch(e){
        console.log(e.message);
    }
    

    callback: 常用的javascript回调

    • 通常作为参数进行传递
    function getData(callback) {
        $.ajax({
            url: '',
            success: resp => {
                callback(resp);
            }
        });
    }
    getData(resp => {
        // write your code here
    });
    
    • 调用的时候,可以直接调用,还可以通过bind,call,apply指定当前作用域
    function getData(callback) {
        $.ajax({
            url: '',
            success: resp => {
                callback(resp.data);
                callback.bind(null)(resp.data);
                callback.call(null, resp.data);
                callback.apply(null, resp.data);
            }
        });
    }
    getData((...resp) => {
        // write your code here
    });
    

    事件监听: 一般用作dom的事件绑定

    let myEvents = new MyEvent();
    myEvents.addEvents({
        once: () => {
            console.log('只会console一次');
            myEvents.removeEvent('once');
        },
        infinity: () => {
            console.log('每次点击,都会console');
        }
    });
    
    document.onclick = e => {
        myEvents.fireEvents(['once', 'infinity']);
    }
    
    • 2.DOM自定义事件
    let elImage = document.getElementById('image');
    $(elImage).addEvent('click', e => {
        e = e || window.event;
        let target = e.target || e.srcElement;
    
        // 元素节点 为1; 元素属性 为2
        if (target.nodeType === 1) {
            console.log(`点击类型:${e.type}`);
            $(target).fireEvent('console');
        }
    })
    
    事件流图片

    发布/订阅: 消息通讯

    • 1.实现一个消息发布
    let subPub = new SubPub();
    subPub.subscribe('getName', name => {
        console.log('your name is: ', name);
    });
    subPub.publish('getName', 'Tom');
    

    1.观察者模式和发布/订阅的区别:

    1.1.Observer模式要求希望接收到主题通知者的观察者必须订阅内容改变的事件

    1.2.Subscribe/Publish模式使用了一个主题/事件通道,这个通道介于订阅者和发布者之间。该事件系统允许代码定义应用程序的特定事件,该事件可以传递自定义参数,自定义参数包含订阅者所需要的值。其目的是避免订阅者和发布者产生依赖关系。

    from: 《Javascript设计模式》

    • 2.nodejs版本的消息发布、订阅
    const EventEmitter = require('events');
    
    class MyEmitter extends EventEmitter {}
    
    const myEmitter = new MyEmitter();
    myEmitter.on('event', (a, b) => {
      console.log(a, b, this);
    });
    myEmitter.emit('event', 'a', 'b');
    
    • 2.1.ES6中import对于循环引用的处理问题

    TODO: require引用?

    • 2.2.?commonJS中require是值的copy?,ES6中import是值的引用

    • 3.更高级的状态管理:redux,vuex

    promise: 回调的代码组织的封装

    1.promise A+规范: wikiplusA+翻译

    2.promise的流程

    3.要点

    • 3.1.Promise 本质是一个状态机。每个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。
    • 3.2.then 方法可以被同一个 promise 调用多次。
    • 3.3.then 方法必须返回一个 promise。

    4.一些问题

    • 4.1.下面四个使用promise语句的不同点在哪里?
    doSomething().then(function () {
        return doSomethingElse();
    }).then(finalHandler);
    
    doSomething().then(function () {
        doSomethingElse();
    }).then(finalHandler);
    
    doSomething().then(doSomethingElse()).then(finalHandler);
    
    doSomething().then(doSomethingElse).then(finalHandler);
    
    • 4.2.新手问题:
    • 4.2.1.callback方式使用promise
    // 不推荐
    somePromise()
    .then(data => {
      anotherPromise()
      .then(anotherData => {
        // write your code here
      })
      .catch(window.console.log.bind(window.console))
    })
    .catch(window.console.log.bind(window.console))
    
    // 推荐
    somePromise()
    .then(data => {
      return anotherPromise().then(data, anotherData);
    })
    then((data, another) => {
    
    })
    .catch(window.console.log.bind(window.console))
    
    let promises = [new Promise(resolve => {
      let dataA = {
        name: 'dataA'
      };
      resolve(dataA);
    }), new Promise(resolve => {
      let dataB = {
        name: 'dataB'
      };
      resolve(dataB);
    })];
    let keys = ['dataA', 'dataB']
    let dataAll = {};
    promises.forEach((promise, index) => {
      promise
      .then(data => {
        dataAll[keys[index]] = data;
      })
      .catch(e => {
        console.log('error: ', e);
      })
    });
    
    • 4.2.3.忘记加catch
    somePromise()
    .then(() => {
      return anotherPromise();
    })
    .then(() => {
      return lastPromise();
    })
    // 没有业务错误需求,加上这句就方便调试
    .catch(console.log.bind(console));
    
    • 4.2.3.不推荐使用deferred(历史包袱),两种方式改正

    • 4.2.3.1.使用第三方的库包装成promise,如angular的$q库:

    $q.when(db.put(doc)).then(...)
    
    • 4.2.3.2.使用promise:
    new Promise(function (resolve, reject) {
        fs.readFile('myfile.txt', function (err, file) {
            if (err) {
                return reject(err);
            }
            resolve(file);
        });
    })
    .then(...)
    
    • 4.2.4.不显示调用return
    somePromise()
    .then(() => {
      anotherPromise();
    })
    .then(data => {
      // data was undefined
    })
    
    • 4.3.进阶错误
    • 4.3.1.不了解Promise.resolve()/Promise.reject();
    • 4.3.2.catch和then(null, reject => {})不完全相同: then中的rejectHandler不会捕获resolveHandler中的错误
    // 1.then reject
    somePromise().then(resolve => {
      throw new Error('error');
    }, reject => {
      // catch nothing
    })
    // 2.catch: this type was recomended
    somePromise()
    .then(resolve => {
      throw new Error('error');
    })
    .catch(e => {
      // catch the error
    })
    
    // 3.the same as below:
    somePromise()
    .then(resolve => {
      throw new Error('error');
    })
    .then(null, e => {
      // catch the error
    })
    
    • 4.3.3.promise vs promise factories: 一个接一个执行一系列的promise
    function executeSequentially(promiseFactories) {
      var result = Promise.resolve();
      promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
      });
      return result;
    }
    // 使用promise工厂
    function myPromiseFactory() {
      return somethingThatCreatesAPromise();
    }
    // 示例:
    let promiseFactories = [];
    promiseFactories.push(myPromiseFactory);
    executeSequentially(promiseFactories);
    
    • 4.3.4.想要两个promise的结果

    • 4.3.4.1.原始代码

    let getUserAndAccount = user => {
      return new Promise((resolve, reject) => {
        getUserAccountById(user.id)
        .then(userAccount => {
          resolve(user, userAccount);
        })
        .catch(reject);
      })
    }
    getUserByName('nolan')
    .then(getUserAndAccount)
    .then(function (user, userAccount) {
      console.log('user and userAccount: ', user, userAccount);
    })
    .cath(e => {
      console.log('error: ', e);
    });
    
    • 4.3.4.2.简化后代码
    let getUserAndAccount = user => getUserAccountById(user.id)
                                    .then(userAccount => Promise.resolve(user, userAccount))
    getUserByName('nolan')
    .then(getUserAndAccount)
    .then(function (user, userAccount) {
      console.log('user and userAccount: ', user, userAccount);
    })
    .cath(e => {
      console.log('error: ', e);
    });
    
    Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
      console.log(result);
    });
    

    5.一些提议

    • 5.1.then方法内部相关:
    • 5.1.1.return一个promise对象。
    • 5.1.2.return一个同步值或者是undefined
    • 5.1.3.同步的throw一个错误
    getUserByName('nolan').then(function (user) {
      if (user.isLoggedOut()) {
        throw new Error('user logged out!'); // throwing a synchronous error!
      }
      return inMemoryCache[user.id] || getUserAccountById(user.id);    // returning a synchronous value or a promise!
    }).then(function (userAccount) {
      // I got a user account!
    }).catch(function (err) {
      // Boo, I got an error!
      if (err) {
        let message = err.message;
        if (~message.indexOf('logged')) {
          // 已经登出的处理逻辑
        } else {
          // 其他的错误处理逻辑
        }
      }
    });
    

    6.一些Promise知识点

    • 6.1.Promise.all, Promise.race
    • 6.1.1.相同点: Promise.race和Promise.all都能接收一个数组
    • 6.1.2.不同点: Promise.race只要有一个reject或者resolve,就立即返回,Promise.all等待所有的resolve,reject,才会返回,如果有一个reject,那么all的结果也是reject的(所有的resolve,才会resolve)
    Promise.all([new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('first');
      }, 1000);
    }), Promise.reject(123), new Promise((resolve, reject) => {
      console.log('second');
      resolve();
    })])
    .then(data => {
      console.log('I am all data: ', data);
    })
    .catch(e => {
      console.log('error', e);
    });
    
    • 6.1.3.使用场景: Promise.race可以在ajax网络超时判断使用
    let timeout = 3e3;
    Promise.race([new Promise((resolve, reject) => {
      $.ajax('url', resp => {
        console.log('ajax resp: ', resp);
      });
    }), new Promise((resolve, reject) => {
      setTimeout(resolve, timeout);
    })]);
    

    6.2.Promise.resolve返回一个已经resolve的promise对象,reject同理

    generator,yeild: 流程控制的新语法

    1.generator的含义与定义: 异步操作的容器

    function* gen(){
        let url = 'https://api.github.com/users/github';
        let result = yield fetch(url);
        console.log('result: ', result.bio);
    }
    
    let genUser = () => {
        let g = gen();
        let result = g.next();
    
        result.value.then(data => {
            let json = data.json();
            return json;
        }).then(data => {
            g.next(data);
        });
    }
    
    • 1.1.函数可以暂停执行和恢复执行

    • 1.2.Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数

    function* f() {
        console.log('执行了!')
    }
    
    var generator = f();
    
    setTimeout(function () {
        generator.next()
    }, 2000);
    
    
    • 1.3.函数体内外的数据交换和?错误处理机制?
    function* gen(x){
      var y = yield x + 2;
      console.log('gen(): ', y, x);
      return y;
    }
    var g = gen(1);
    var value = g.next();
    console.log('value: ', value);
    var value2 = g.next(12);
    console.log('value2: ', value2);
    
    • 1.4.yield表达式只能用在 Generator 函数里面
    function f(param) {
        let a = yield 3 * param;
    }
    

    2.Thunk函数的含义与定义: 可以在回调函数里,将执行权交还给 Generator 函数,生产环境推荐thunkify

    var gen = function* (){
      var f1 = yield readFile('fileA');
      var f2 = yield readFile('fileB');
      // ...
      var fn = yield readFile('fileN');
    };
    
    run(gen);
    
    • 2.thunk函数介绍: 诞生于上个60年代

    • 2.1.1.传值调用

    let f = (a, b) => b;
    
    f(3 * x * x - 2 * x - 1, x);
    
    • 2.1.2.传名调用
    let f = m => m * 2;
    
    f(x + 5);
    
    60年代就诞生
    // 等同于
    
    let thunk () => (x + 5);
    
    let f = thunk => (thunk() * 2);
    
    • 2.1.3.thunkify源码:
    function thunkify(fn){
      return function(){
        let args = Array.prototype.slice.call(arguments);
        let ctx = this;
    
        return function(done){
          // 检查机制: 确保回调函数只运行一次
          let called;
    
          args.push(function(){
            if (called) return;
            called = true;
            done.apply(null, arguments);
          });
    
          try {
            fn.apply(ctx, args);
          } catch (err) {
            done(err);
          }
        }
      }
    };
    
    • 2.1.4.thunk与generator结合:
    let fs = require('fs');
    let thunkify = require('thunkify');
    let readFile = thunkify(fs.readFile);
    
    let gen = function* (){
      let r1 = yield readFile('/etc/fstab');
      console.log(r1.toString());
      let r2 = yield readFile('/etc/shells');
      console.log(r2.toString());
    };
    
    • 2.1.5.手动执行:
    let g = gen();
    
    let r1 = g.next();
    r1.value(function(err, data){
      if (err) throw err;
      let r2 = g.next(data);
      r2.value(function(err, data){
        if (err) throw err;
        g.next(data);
      });
    });
    
    • 2.1.6.结合:
    function run(fn) {
      let gen = fn();
    
      function next(err, data) {
        let result = gen.next(data);
        if (result.done) return;
        result.value(next);
      }
    
      next();
    }
    
    run(gen);
    

    3.co函数库的含义与定义: Generator 函数的执行器, yield后必须是thunk/promise函数

    var gen = function* (){
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
    var co = require('co');
    co(gen);
    
    • 3.1.协程与事件循环: 控制流的主动让出和恢复

    • 3.1.1.提出时间: 1963; 提出人: Melvin Conway

    • 3.1.2.历程: 进程->线程->用户态线程->协程

    • 3.1.3.名词释义:

    • 3.1.3.1.进程: 代码,被代码控制的资源(内存,I/O,文件)两大基本元素等组成的实体,两大特性[掌控资源,可以被调度]

    • 3.1.3.2.线程: 程在进程内部,处理并发的逻辑,拥有独立的栈,却共享线程的资源

    • 3.1.3.3.用户态线程: 线程切换的时候,进程需要为了管理而切换到内核态,处理状态转换(性能消耗严重)

    • 3.1.4.没火的原因: 命令式编程(自顶向下开发,子历程作为唯一控制结构)、函数式编程[意气之争]

    • 3.1.5.关系: 子历程是没有使用yield的协程。Donald Ervin Knuth(wiki)/Donald Ervin Knuth(baidu): 子历程是协程的一种特例

    • 3.2.没有异常处理的简化版co函数

    function co(gen){
        let def = Promise.defer();
        let iter = gen();
    
        function resolve(data) {
            // 恢复迭代器并带入promise的终值
            step(iter.next(data));
        }
    
        function step(it) {
            it.done ?
                // 迭代结束则解决co返回的promise
                def.resolve(it.value) :
                // 否则继续用解决程序解决下一个让步出来的promise
                it.value.then(resolve);
        }
    
        resolve();
        return def.promise;
    }
    
    • 3.3.使用co, yield后面放的必须是thunk/promise函数

    async,await: generator的语法糖

    async的含义与定义

    let getData = () => {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: 'json/test.json',
                method: 'GET',
                success: function (resp) {
                    // data = resp.data;
                    resolve(resp);
                },
                error: function (error) {
                    reject(error);
                }
            });
        });
    }
    
    async function initView(){
        try {
            let resp = await getData();
            console.log(resp);
        } catch (e) {
            console.error(e);
        }
    }
    initView();
    

    async的一些问题

    1.同时触发:

    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    

    最后的一些问题与思考

    1.从异步操作上,async是最后演化的结果,callback是就不用了、还是应该尽量避免?

    参考资料

    扩展阅读

    相关文章

      网友评论

          本文标题:callback

          本文链接:https://www.haomeiwen.com/subject/uztsmctx.html