callback
前言
setInterval: 另類的callback實(shí)現(xiàn)
- setInterval同級(jí)別的另外一個(gè)函數(shù):setTimeout。
- 設(shè)置n秒后,有一定時(shí)間延時(shí)的,2ms左右;
- 最低時(shí)間為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中的錯(cuò)誤使用try,catch不可捕獲
try{
setTimeout(function(){
throw new Error("我不希望這個(gè)錯(cuò)誤出現(xiàn)!")
}, 1000);
} catch(e){
console.log(e.message);
}
callback: 常用的javascript回調(diào)
- 通常作為參數(shù)進(jìn)行傳遞
function getData(callback) {
$.ajax({
url: '',
success: resp => {
callback(resp);
}
});
}
getData(resp => {
// write your code here
});
- 調(diào)用的時(shí)候,可以直接調(diào)用,還可以通過bind,call,apply指定當(dāng)前作用域
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
});
事件監(jiān)聽: 一般用作dom的事件綁定
let myEvents = new MyEvent();
myEvents.addEvents({
once: () => {
console.log('只會(huì)console一次');
myEvents.removeEvent('once');
},
infinity: () => {
console.log('每次點(diǎn)擊,都會(huì)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;
// 元素節(jié)點(diǎn) 為1; 元素屬性 為2
if (target.nodeType === 1) {
console.log(`點(diǎn)擊類型:${e.type}`);
$(target).fireEvent('console');
}
})

發(fā)布/訂閱: 消息通訊
- 1.實(shí)現(xiàn)一個(gè)消息發(fā)布
let subPub = new SubPub();
subPub.subscribe('getName', name => {
console.log('your name is: ', name);
});
subPub.publish('getName', 'Tom');
1.觀察者模式和發(fā)布/訂閱的區(qū)別:
1.1.Observer模式要求希望接收到主題通知者的觀察者必須訂閱內(nèi)容改變的事件
1.2.Subscribe/Publish模式使用了一個(gè)主題/事件通道,這個(gè)通道介于訂閱者和發(fā)布者之間。該事件系統(tǒng)允許代碼定義應(yīng)用程序的特定事件,該事件可以傳遞自定義參數(shù),自定義參數(shù)包含訂閱者所需要的值。其目的是避免訂閱者和發(fā)布者產(chǎn)生依賴關(guān)系。
from: 《Javascript設(shè)計(jì)模式》
- 2.nodejs版本的消息發(fā)布、訂閱
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對(duì)于循環(huán)引用的處理問題
TODO: require引用?
promise: 回調(diào)的代碼組織的封裝
1.promise A+規(guī)范: wiki、plus、A+翻譯
2.promise的流程
3.要點(diǎn)
- 3.1.Promise 本質(zhì)是一個(gè)狀態(tài)機(jī)。每個(gè) promise 只能是 3 種狀態(tài)中的一種:pending、fulfilled 或 rejected。狀態(tài)轉(zhuǎn)變只能是 pending -> fulfilled 或者 pending -> rejected。狀態(tài)轉(zhuǎn)變不可逆。
- 3.2.then 方法可以被同一個(gè) promise 調(diào)用多次。
- 3.3.then 方法必須返回一個(gè) promise。
4.一些問題
- 4.1.下面四個(gè)使用promise語句的不同點(diǎn)在哪里?
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,應(yīng)該使用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();
})
// 沒有業(yè)務(wù)錯(cuò)誤需求,加上這句就方便調(diào)試
.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.不顯示調(diào)用return
somePromise()
.then(() => {
anotherPromise();
})
.then(data => {
// data was undefined
})
- 4.3.進(jìn)階錯(cuò)誤
- 4.3.1.不了解Promise.resolve()/Promise.reject();
- 4.3.2.catch和then(null, reject => {})不完全相同: then中的rejectHandler不會(huì)捕獲resolveHandler中的錯(cuò)誤
// 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: 一個(gè)接一個(gè)執(zhí)行一系列的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.想要兩個(gè)promise的結(jié)果
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方法內(nèi)部相關(guān):
- 5.1.1.
return一個(gè)promise對(duì)象。 - 5.1.2.
return一個(gè)同步值或者是undefined - 5.1.3.同步的
throw一個(gè)錯(cuò)誤
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')) {
// 已經(jīng)登出的處理邏輯
} else {
// 其他的錯(cuò)誤處理邏輯
}
}
});
6.一些Promise知識(shí)點(diǎn)
- 6.1.Promise.all, Promise.race
- 6.1.1.相同點(diǎn): Promise.race和Promise.all都能接收一個(gè)數(shù)組
- 6.1.2.不同點(diǎn): Promise.race只要有一個(gè)reject或者resolve,就立即返回,Promise.all等待所有的resolve,reject,才會(huì)返回,如果有一個(gè)reject,那么all的結(jié)果也是reject的(所有的resolve,才會(huì)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網(wǎng)絡(luò)超時(shí)判斷使用
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返回一個(gè)已經(jīng)resolve的promise對(duì)象,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.函數(shù)可以暫停執(zhí)行和恢復(fù)執(zhí)行
1.2.Generator 函數(shù)可以不用yield表達(dá)式,這時(shí)就變成了一個(gè)單純的暫緩執(zhí)行函數(shù)
function* f() {
console.log('執(zhí)行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
- 1.3.函數(shù)體內(nèi)外的數(shù)據(jù)交換和?錯(cuò)誤處理機(jī)制?
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表達(dá)式只能用在 Generator 函數(shù)里面
function f(param) {
let a = yield 3 * param;
}
2.Thunk函數(shù)的含義與定義: 可以在回調(diào)函數(shù)里,將執(zhí)行權(quán)交還給 Generator 函數(shù),生產(chǎn)環(huán)境推薦thunkify
var gen = function* (){
var f1 = yield readFile('fileA');
var f2 = yield readFile('fileB');
// ...
var fn = yield readFile('fileN');
};
run(gen);
2.thunk函數(shù)介紹: 誕生于上個(gè)60年代
2.1.1.傳值調(diào)用
let f = (a, b) => b;
f(3 * x * x - 2 * x - 1, x);
- 2.1.2.傳名調(diào)用
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){
// 檢查機(jī)制: 確?;卣{(diào)函數(shù)只運(yùn)行一次
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結(jié)合:
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.手動(dòng)執(zhí)行:
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.結(jié)合:
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函數(shù)庫的含義與定義: Generator 函數(shù)的執(zhí)行器, yield后必須是thunk/promise函數(shù)
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.協(xié)程與事件循環(huán): 控制流的主動(dòng)讓出和恢復(fù)
3.1.1.提出時(shí)間: 1963; 提出人: Melvin Conway
3.1.2.歷程: 進(jìn)程->線程->用戶態(tài)線程->協(xié)程
3.1.3.名詞釋義:
3.1.3.1.進(jìn)程: 代碼,被代碼控制的資源(內(nèi)存,I/O,文件)兩大基本元素等組成的實(shí)體,兩大特性[掌控資源,可以被調(diào)度]
3.1.3.2.線程: 程在進(jìn)程內(nèi)部,處理并發(fā)的邏輯,擁有獨(dú)立的棧,卻共享線程的資源
3.1.3.3.用戶態(tài)線程: 線程切換的時(shí)候,進(jìn)程需要為了管理而切換到內(nèi)核態(tài),處理狀態(tài)轉(zhuǎn)換(性能消耗嚴(yán)重)
3.1.4.沒火的原因: 命令式編程(自頂向下開發(fā),子歷程作為唯一控制結(jié)構(gòu))、函數(shù)式編程[意氣之爭]
3.1.5.關(guān)系: 子歷程是沒有使用yield的協(xié)程。Donald Ervin Knuth(wiki)/Donald Ervin Knuth(baidu): 子歷程是協(xié)程的一種特例
3.2.沒有異常處理的簡化版co函數(shù)
function co(gen){
let def = Promise.defer();
let iter = gen();
function resolve(data) {
// 恢復(fù)迭代器并帶入promise的終值
step(iter.next(data));
}
function step(it) {
it.done ?
// 迭代結(jié)束則解決co返回的promise
def.resolve(it.value) :
// 否則繼續(xù)用解決程序解決下一個(gè)讓步出來的promise
it.value.then(resolve);
}
resolve();
return def.promise;
}
- 3.3.使用co, yield后面放的必須是thunk/promise函數(shù)
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.同時(shí)觸發(fā):
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
最后的一些問題與思考
1.從異步操作上,async是最后演化的結(jié)果,callback是就不用了、還是應(yīng)該盡量避免?