異步編程

函數(shù)式編程

在JavaScript中,函數(shù)作為一等公民,使用上十分自由,無論調(diào)用它,或者作為參數(shù),或者作為返回值均可。

高階函數(shù)

高階函數(shù)是可以把函數(shù)作為參數(shù),或是將函數(shù)作為返回值的函數(shù)。函數(shù)式編程就是指這種高度抽象的編程范式,是JavaScript異步編程的基礎(chǔ)。

變量指向函數(shù),函數(shù)名也是變量。

function foo(x, bar) {
  return bar(x);
}

對于相同的foo()函數(shù),傳入的bar參數(shù)不同,則可以得到不同的結(jié)果。這這里可以看出高階函數(shù)的靈活性,結(jié)合Node提供的最基本的事件模塊可以看到,事件的處理方式正是基于高階函數(shù)的特性來完成的,在自定義事件實(shí)例中,通過為相同事件注冊不同的回調(diào)函數(shù),可以很靈活地處理業(yè)務(wù)邏輯。

var emitter = new events.EventEmitter();
emitter.on('event_foo', function(){
  // todo something
})

偏函數(shù)用法

var toString = Object.prototype.toString;

var isString = function (obj) {
  return toString.call(obj) == '[object String]';
}

var isFunction = function (obj) {
  return toString.call(obj) == '[object Function]';
}

// 改寫為

var isType = function(type) {
  return function(obj) {
    return toString.call(obj) == `[object ${type}]`
  }
}

var isString = isType('String')
var isFunction = isType('Function')

這種通過指定部分參數(shù)來產(chǎn)生一個新的定制函數(shù)的形式就是偏函數(shù)應(yīng)用。

固定一個函數(shù)的一些參數(shù),然后產(chǎn)生另一個更小元的函數(shù)。

什么是元?元是指函數(shù)參數(shù)的個數(shù),比如一個帶有兩個參數(shù)的函數(shù)被稱為二元函數(shù)。

函數(shù)柯里化

柯里化是將一個多參數(shù)函數(shù)轉(zhuǎn)換成多個單參數(shù)函數(shù),也就是將一個 n 元函數(shù)轉(zhuǎn)換成 n 個一元函數(shù)。

接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)的函數(shù),返回接受處理余下的參數(shù)且返回結(jié)果的新函數(shù)。

// 示意而已
function ajax(type, url, data) {
  var xhr = new XMLHttpRequest();
  xhr.open(type, url, true);
  xhr.send(data);
}

// 雖然 ajax 這個函數(shù)非常通用,但在重復(fù)調(diào)用的時候參數(shù)冗余
ajax('POST', 'www.test.com', "name=kevin")
ajax('POST', 'www.test2.com', "name=kevin")
ajax('POST', 'www.test3.com', "name=kevin")

// 利用 curry
var ajaxCurry = curry(ajax);

// 以 POST 類型請求數(shù)據(jù)
var post = ajaxCurry('POST');
post('www.test.com', "name=kevin");

// 以 POST 類型請求來自于 www.test.com 的數(shù)據(jù)
var postFromTest = post('www.test.com');
postFromTest("name=kevin");

可以理解為:參數(shù)復(fù)用。本質(zhì)上是降低通用性,提高適用性。

var person = [{name: 'kevin'}, {name: 'daisy'}]

var name = person.map(function (item) {
    return item.name;
})

// 使用curry
var prop = curry(function (key, item) {
    return item[key]
});

var name = person.map(prop('name'))


// prop('name') 此時就是
function (item) {
  return item['name'];
}
function curry(fn, args) {
  var length = fn.length;
  args = args || [];

  return function(...innerArgs) {
    var _args = [...args, ...innerArgs];

    if (_args.length < length) {
      return curry.call(this, fn, _args);
    }
    return fn.apply(this, _args);
  }
}

var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (...remainingArg) => judge(...args, ...remainingArg);

異步編程的優(yōu)勢和難點(diǎn)

優(yōu)勢

Node帶來的最大特性莫過于基于事件驅(qū)動的非阻塞I/O模型,非阻塞I/O可以使CPU和I/O并不相互依賴等待,讓資源得到更好的應(yīng)用。

Node實(shí)現(xiàn)異步I/O的原理:利用時間循環(huán)的方式,JavaScript線程像一個分配任務(wù)和處理結(jié)果的大管家,I/O線程池里的各個I/O線程都是小二,負(fù)責(zé)完成分配來的任務(wù),小二和管家之間互不依賴,所以可以保持整體的高效率。

這個模型的缺點(diǎn)則在于管家無法承擔(dān)過多的細(xì)節(jié)性任務(wù),如果承擔(dān)太多,則會影響任務(wù)的調(diào)度。由于事件循環(huán)模型需要應(yīng)對海量請求,海量請求同時作用于在單線程上,就需要放置任何一個計算耗費(fèi)過多的CPU時間片,至于是計算密集型還是I/O密集型,只要不影響異步I/O的調(diào)度,那就不構(gòu)成問題。

難點(diǎn)1:異常處理

異步I/O的實(shí)現(xiàn)主要包含兩個階段,提交請求和處理結(jié)果,這兩個階段中間有事件循環(huán),兩者彼此不關(guān)聯(lián),異步方法則通常在第一個階段提交請求后立即返回,因此異常并不一定發(fā)生在這個階段,所以如果使用try/catch包裹提交請求這一階段,那么對處理結(jié)果的callback執(zhí)行時拋出的異常將無能為力。

Node在處理異常上形成了一種約定,將異常作為回調(diào)函數(shù)的第一個實(shí)參傳回,如果為空值,則表明異步調(diào)用沒有異常拋出。

在我們執(zhí)行編寫的異步方法上,也需要去遵循這樣一些原則:

原則一:必須執(zhí)行調(diào)用者傳入的回調(diào)函數(shù)

原則二:正確傳遞回異常功調(diào)用者判斷

var asynsFn = function(callback) {
  try {
    // do something
    var result = xxx;
  } catch(err) {
    err.status = 400;
    return callback(err);
  }
  callback(null, result)
}

難點(diǎn)2:嵌套過深

對于Node而言,事務(wù)中存在多個異步調(diào)用的場景比比皆是,如果操作存在依賴關(guān)系,那么函數(shù)嵌套行為情有可原,如果操作不存在依賴關(guān)系,那么在一個異步調(diào)用的回調(diào)中調(diào)用另外一個異步的操作也應(yīng)該盡量避免(如下例),因?yàn)檫@沒有利用好異步I/O帶來的并行優(yōu)勢。

fs.readFile(template_path, 'utf8', function (err, template) { 
  db.query(sql, function (err, data) {
    l10n.get(function (err, resources) { 
      // TODO
    });
  });
});

Node提供了絕大部分的異步API和少量的同步API,那么回調(diào)也必不可少,編寫代碼時不注意就很可能會造成過深的嵌套。

難點(diǎn)3:阻塞代碼

在JavaScript中沒有sleep()這樣的線程沉睡功能,所以多半開發(fā)者會寫出下述這樣的代碼來實(shí)現(xiàn)sleep(1000)的效果

var start = new Date();
while (new Date() - start < 1000) {
}
// TODO 需要阻塞的代碼

但是事實(shí)是糟糕的,這段代碼會持續(xù)占用CPU進(jìn)行判斷,與真正的線程沉睡相去甚遠(yuǎn),完全破壞了事件循環(huán)的調(diào)度,由于Node單線程的原因,CPU資源全都會用于為這段代碼服務(wù),導(dǎo)致其余任何請求都會得不到響應(yīng),所以在這種情況下,使用setTimeout()的效果會更好。

難點(diǎn)4:多線程編程

在瀏覽器中的單線程指的是JavaScript執(zhí)行線程與UI渲染共用的一個線程,在Node中,只是沒有UI渲染部分,模型基本相同。

對于服務(wù)器端而言,如果服務(wù)器是多核CPU,單個Node進(jìn)程實(shí)質(zhì)上是沒有充分利用多核CPU的,隨著業(yè)務(wù)的復(fù)雜化,瀏覽器提出了Web Workers,它將通過將JavaScript執(zhí)行與UI渲染分離,可以很好的利用多核CPU為大量計算服務(wù)。同時前端Web Workers也是一個利用消息機(jī)制合理使用多核CPU的理想模型。

Node借鑒了這個模式,child_process是其基礎(chǔ)API,cluster模塊是更深層次的應(yīng)用,因此,如果使用這種模型,開發(fā)人員需要更多地去面臨跨線程的編程,這對于以往的JavaScript編程經(jīng)驗(yàn)是較少考慮的。

難點(diǎn)5:異步轉(zhuǎn)同步

Node的異步API占了絕大部分,偶爾出現(xiàn)的同步需求將會因?yàn)闆]有同步API讓開發(fā)者無所適從,目前,Node中試圖同步式編程,但并不能得到原生支持,需要借助庫或者編譯手段來實(shí)現(xiàn),但對于異步調(diào)用,通過良好的流程控制,還是能夠?qū)⑦壿嬍崂沓身樞蚴降男问健?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Node 的特點(diǎn):事件驅(qū)動、非阻塞I/O 異步I/O; 事件(輕量級、松耦合、只關(guān)注事務(wù)點(diǎn))與回調(diào)函數(shù); 單線程:...
    獨(dú)木舟的木閱讀 449評論 0 0
  • 1.函數(shù)式編程 1.1高階函數(shù) 函數(shù)參數(shù)只接受基本數(shù)據(jù)類型或者對象引用,返回值也是基本數(shù)據(jù)類型和對象引用。 高階函...
    maikuraki閱讀 269評論 0 1
  • Java是一種可以撰寫跨平臺應(yīng)用軟件的面向?qū)ο蟮某绦蛟O(shè)計語言。Java 技術(shù)具有卓越的通用性、高效性、平臺移植性和...
    Java小辰閱讀 742評論 1 0
  • 還記得一年前寫過一篇關(guān)于JavaScript異步編程簡述的文章,主要介紹了JavaScript的單線程特性與異步編...
    極樂君閱讀 442評論 1 7
  • 做好眼下的事吧!其他的,再說~
    麥新閱讀 147評論 0 0

友情鏈接更多精彩內(nèi)容