2 Star 1 Fork 0

muzi131313 / callback

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

#callback

前言

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的流程

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是就不用了、还是应该尽量避免?

参考资料

扩展阅读

The MIT License (MIT) Copyright (c) 2017 muzi131313 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

callback的demo 展开 收起
JavaScript
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
JavaScript
1
https://gitee.com/muzi131313/callback.git
git@gitee.com:muzi131313/callback.git
muzi131313
callback
callback
master

搜索帮助