非同步的發展,順手學會怎麼處理多請求
- 非同步:先幹一件事,中間去幹其他的事,最終再回來幹這件事
- 高階函式是函式作為引數或者函式作為返回值 ,作用是批量生成函式和預置函式做為引數(可以快取函式,當達到條件時執行該函式)
- 非同步的發展流程:callback ->promise -> generator + co -> async+await(語法糖)
- 回撥函式金字塔處理多請求 -> 哨兵函式處理多請求 -> promise處理多請求 -> co處理多請求 -> async處理多請求
- 手寫實現co、bluebird的promisify和promisifyAll
非同步的發展流程
-
非同步:先幹一件事 中間去幹其他的事,最終在回來幹這件事
-
同步:同步連續執行
-
非同步的發展流程:callback ->promise -> generator + co -> async+await(語法糖)
-
非同步發展的最終結果就是,像同步一樣的寫法,簡單優雅易懂
回撥函式的金字塔地獄版本1.0
普通的讀到2個檔案之後才能進行某件事,可能最開始的手段:
// 本地寫3檔案,index.js寫以下程式碼 template.txt寫些html的程式碼,data.txt寫些json資料,然後命令列執行 node index.js let fs = require('fs') fs.readFile('template.txt','utf8',function(err,template){ // error-first fs.readFile('data.txt','utf8',function(err,data){ // error-first console.log({template:template,data:data}); }); }); 複製程式碼
理解高階函式
先介紹高階函式的含義和用法。
-
含義:函式作為引數或者函式作為返回值
-
用法:批量生成函式和預置函式做為引數
批量生成函式
比如判斷變數是不是物件或者陣列
function isObject(content){ return Object.prototype.toString.call(content) === '[object Object]'; } function isArray(content){ return Object.prototype.toString.call(content) === '[object Array]'; } 複製程式碼
但這樣一個個寫很麻煩,可以寫一個函式生成這些函式,這樣簡單粗暴。在平時你發現函式裡有重複程式碼的時候,可以考慮封裝一個高階函式生成函式~
function isType(type){ return function(content){ return Object.prototype.toString.call(content) === `[object ${type}]`; } } const isObject = isType('Object') const isArray = isType('Array') 複製程式碼
預置函式做為引數
lodash裡面有個after的函式,功能是函式呼叫幾次之後才真正執行函式,很神奇是吧,走一個~
function after(times,fn){ return function(){ if(--times===0){ fn() } } } let eat = after(3,function(){ console.log('飽了') }) eat(); eat(); eat(); // 這次才會執行 複製程式碼
舉一反三,換句話說這樣可以快取函式,當達到條件時執行該函式。這就超級厲害了~
高階函式的哨兵變數版2.0
由上面例子得到的啟發,再看前面的例子
// 本地寫3檔案,index.js寫以下程式碼 template.txt寫些html的程式碼,data.txt寫些json資料,然後命令列執行 node index.js function after(requestCounts,fn){ let dataSet = {} // 資料收集,請求跟結果一一對應,所以存為物件,這個變數通常稱為哨兵變數 // return的函式就是單個讀取到結果之後在其回撥函式裡執行的函式,所以可以拿到資料 return function(key,data){ dataSet[key] = data // 所有請求都拿到結果之後 if(Object.keys(dataSet).length ===requestCounts){ fn(dataSet) } } } let out = after(2,function(res){ console.log(res); }) let fs = require('fs') fs.readFile('template.txt','utf8',function(err,data){out('template',data)}) fs.readFile('data.txt','utf8',function(err,data){out('data',data)}); 複製程式碼
這樣很方便處理併發請求,請求的數量傳入即可。
理解promise
可以對照promise的網站,自己試著實現promise。
// 大概用法 var y = new Promise((resolve,reject)=>{ setTimeout(()=>{ let x = Math.random() if(x >0.5){ resolve(x) }else{ reject(x) } },100) }) console.log(y) var yThen = y.then((data)=>{ console.log('then',data) },(data)=>{ console.log('catch',data) }) 複製程式碼
promise版本3.0
let fs = require('fs') function readFilePro(filename){ return new Promise((resolve,reject)=>{ fs.readFile(filename,'utf8',function(err,data){err?reject(err):resolve(data)}); }) } Promise.all([readFilePro('template.txt'),readFilePro('data.txt')]).then(res=>{ console.log({template:res[0],data:res[1]}) }) 複製程式碼
理解生成器generator
生成器函式雖然是一個函式,但和普通函式不一樣,普通函式一旦呼叫就會執行完。
- 生成器函式用* 來標識
- 呼叫的結果是一個迭代器 迭代器有一個next方法
- 遇到暫停點yield就停下來,直到執行迭代器的next,最後才能返回這個函式的return
- yield後面跟著的是value的值
- yield等號前面的是我們當前呼叫next傳進來的值
- 第一次next傳值是無效的
- 當done為true的時候就是value就是生成器return的值
// 生成器函式有個特點需要加個* function *go(a){ console.log(1) // 此處b是外界輸入,這行程式碼實現輸入輸出 let b = yield a console.log(2) let c = yield b console.log(3) return 'o' } // 生成器函式和普通函式不一樣呼叫他函式不會立刻執行 // 返回生成器的迭代器,迭代器是一個物件 let it = go('a') // next第一次執行不需要傳引數,想想也是,沒有意義 let r1 = it.next() console.log(r1) // {value:'a',done:false} let r2 = it.next('B') console.log(r2) // {value:'B',done:false} let r3 = it.next('C') // 當done為true的時候就是return的值 console.log(r3) // {value:'o',done:true} 複製程式碼
理解co,讓生成器自動執行
co是大神tj 寫出來的,超棒的小夥子啊,才23歲好像,再次感慨人與人之間的差距簡直比人與狗之間的差距還大,麵條淚~
co讓生成器自動執行的原理其實想想就是讓next執行到結束為止。
!!!!必須特別強調: co 有個使用條件,generator 函式的 yield 命令後面,只能是 Thunk 函式或 Promise 物件。
// gen是生成器generator的簡寫 function co(gen){ let it = gen() return new Promise((resolve,reject)=>{ !function next(lastValue){ let {value,done} = it.next(lastValue) if(done){ resolve(value) }else{ // 遞迴,這裡也看出來,這也是為啥yield後面必須是promise型別 value.then(next) } }() }) } co(go) 複製程式碼
promise和co版本的4.0
// 本地寫3檔案,index.js寫以下程式碼 template.txt寫些html的程式碼,data.txt寫些json資料,然後命令列執行 node index.js let fs = require('fs') function readFilePro(filename){ return new Promise((resolve,reject)=>{ fs.readFile(filename,'utf8',function(err,data){err?reject(err):resolve(data)}); }) } function *gen(){ // let res = {} let template = yield readFilePro('template.txt') let data = yield readFilePro('data.txt') return {template,data} } // 也可以直接引入 co的庫 npm i colet co = require('co') function co(gen){ let it = gen() return new Promise((resolve,reject)=>{ !function next(lastValue){ let {value,done} = it.next(lastValue) if(done){ resolve(value) }else{ // 遞迴,這裡也看出來,這也是為啥yield後面必須是promise型別 value.then(next) } }() }) } co(gen).then(res=>console.log(res)) 複製程式碼
async和await版本5.0
async和await是promise和generator的語法糖。其實go函式就是gen函式裡面的yield變成await~
因為async函式其實有點co的感覺,await後面必須是promise~
// 本地寫3檔案,index.js寫以下程式碼 template.txt寫些html的程式碼,data.txt寫些json資料,然後命令列執行 node index.js let fs = require('fs') // readFilePro也可以用bluebird生成 function readFilePro(filename){ return new Promise((resolve,reject)=>{ fs.readFile(filename,'utf8',function(err,data){err?reject(err):resolve(data)}); }) } async function go(){ let template = await readFilePro('template.txt') let data = await readFilePro('data.txt') // 這裡的return必須用then才能拿到值,因為是語法糖啊~ return {template,data} } go().then(res=>console.log(res)) 複製程式碼
這也是最終版啦,非同步寫成同步的感覺~
bluebird
再叨叨點bluebird,它能把任意通過回撥函式實現的非同步API換成promiseApi。
常用的方法兩個:promisify和promisifyAll。
promisify將回調函式實現的非同步API換成promiseApi。
promisifyAll遍歷物件上所有的方法 然後對每個方法新增一個新的方法 Async。
let fs = require('fs') // npm i bluebird let Promise = require('bluebird') let readFilePro = Promise.promisify(fs.readFile) // 好像很眼熟是不是 哈哈哈哈 readFilePro('template.txt','utf8').then((template)=>{console.log(template)}) Promise.promisifyAll(fs) // console.log(fs) // 發現fs的方法多了 fs.readFileAsync('template.txt','utf8').then((template)=>{console.log(template)}) 複製程式碼
其實感覺可以手寫實現的有木有,來走一個~
let fs = require('fs') // 先看簡單版的 function readFilePro(filename,encode){ return new Promise((resolve,reject)=>{ fs.readFile(filename,encode,function(err,data){err?reject(err):resolve(data)}); }) } // 高階函式生成上面的函式 function promisify(fn){ // 這裡生成readFilePro類似的函式,這裡因為引數不一定,所以用args return function(...args){ return new Promise((resolve,reject)=>{ // 因為回撥函式在最後一個,所以用拼接的方式,call的用法知道哈~ fn.call(null,...args,function(err,data){err?reject(err):resolve(data)}) }) } } function promisifyAll(object){ for (const key in object) { if (object.hasOwnProperty(key) && typeof object[key]==='function') { object[`${key}Async`] = promisify(object[key]) } } return object } let readFilePro = promisify(fs.readFile) // 好像很眼熟是不是 哈哈哈哈 readFilePro('template.txt','utf8').then((template)=>{console.log(template)}) promisifyAll(fs) // console.log(fs) fs.readFileAsync('template.txt','utf8').then((template)=>{console.log(template)}) 複製程式碼