共计 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 函数式编程指南
正文完
发表至: javascript
2019-10-15