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的事件绑定
- 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');
}
})
发布/订阅: 消息通讯
- 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+规范: 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
// anotherPromise依赖somePromise
// 不推荐
somePromise()
.then(data => {
anotherPromise(data.id)
.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(data.id).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);
})
});
// 推荐
Promise
.all(promises)
.then(data => {
// [dataA, dataB]
})
- 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);
});
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.错误处理机制
function* gen(x){
var y;
try{
y = yield x + 2;
console.log('gen(): ', y, x);
}catch(e){
console.log(e);
}
return y;
}
var g = gen(1);
var value = g.next();
console.log('value: ', value);
var value2 = g.throw(new Error('error'));
- 1.5.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);
// 等同于
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(baike): 子历程是协程的一种特例
- 3.2.使用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 Demo Implement
- JavaScript Promise迷你书(中文版)
- mdn Promise
- JavaScript异步编程原理
- 漫谈js自定义事件、DOM/伪DOM自定义事件(张鑫旭)
- js原生创建模拟事件和自定义事件
- NodeJS Event
- NodeJS EventEmitter
- JS发布/订阅简单实现