callback

16次阅读

共计 10376 个字符,预计需要花费 26 分钟才能阅读完成。

callback

前言

  • ECMAScript 6 入门(阮一峰)

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 的事件绑定

  • 1.js 自定义事件监听:
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');
    }
})
  • 2.1.nodeType:
  • 2.2.DOM 事件流:

发布 / 订阅: 消息通讯

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

1. 观察者模式和发布 / 订阅的区别:<br/>
1.1.Observer 模式要求希望接收到主题通知者的观察者必须订阅内容改变的事件 <br/>
1.2.Subscribe/Publish 模式使用了一个主题 / 事件通道,这个通道介于订阅者和发布者之间。该事件系统允许代码定义应用程序的特定事件,该事件可以传递自定义参数,自定义参数包含订阅者所需要的值。其目的是避免订阅者和发布者产生依赖关系。<br/>
from:《Javascript 设计模式》<br/>

  • 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+ 规范: wiki、plus、A+ 翻译

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))
  • 4.2.2.forEach 使用 promise, 应该使用 Promise.all
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);
});
  • 4.3.5. 值穿透
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {console.log(result);
});
  • 4.3.6. 不能 cancel?,issue70, proposal-cancelable-promises

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

参考资料

  • Node.js 最新技术栈之 Promise 篇
  • Promise 实现原理
  • 详解 ES6 中的 Promise 与异步编程
  • 深入 Promise
  • 你可能不知道的 Promise
  • 谈谈使用 promise 时候的一些反模式(EFE)
  • Promise Demo Implement
  • Promise Demo Implement for Question
  • JavaScript Promise 迷你书(中文版)
  • mdn Promise
  • JavaScript Promises … In Wicked Detail
  • JavaScript 异步编程原理
  • 深入掌握 ECMAScript 6 异步编程系列(阮一峰)
  • 漫谈 js 自定义事件、DOM/ 伪 DOM 自定义事件(张鑫旭)
  • js 原生创建模拟事件和自定义事件
  • JS 观察者模式
  • NodeJS Event
  • NodeJS EventEmitter
  • JS 发布 / 订阅简单实现

扩展阅读

  • JS 函数式编程指南

正文完
 0