callback

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設計模式》
vue

  • 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對於循環引用的處理問題java

    TODO: require引用?node

  • 2.2.?commonJS中require是值的copy?,ES6中import是值的引用react

  • 3.更高級的狀態管理:redux,vuexgit

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(歷史包袱),兩種方式改正es6

  • 4.2.3.1.使用第三方的庫包裝成promise,如angular的$q庫:github

$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的結果ajax

  • 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是就不用了、仍是應該儘可能避免?

參考資料

擴展閱讀

相關文章
相關標籤/搜索