異步:先幹一件事 中間去幹其餘的事,最終在回來幹這件事html
同步:同步連續執行node
異步的發展流程:callback -> promise -> generator + co -> async+await(語法糖)git
異步發展的最終結果就是,像同步同樣的寫法,簡單優雅易懂github
普通的讀到2個文件以後才能進行某件事,可能最開始的手段:npm
// 本地寫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});
});
});
複製代碼
先介紹高階函數的含義和用法。json
含義:函數做爲參數或者函數做爲返回值數組
用法:批量生成函數和預置函數作爲參數promise
好比判斷變量是否是對象或者數組緩存
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(); // 此次纔會執行
複製代碼
觸類旁通,換句話說這樣能夠緩存函數,當達到條件時執行該函數。這就超級厲害了~
由上面例子獲得的啓發,再看前面的例子
// 本地寫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。
// 大概用法
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)
})
複製代碼
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]})
})
複製代碼
生成器函數雖然是一個函數,但和普通函數不同,普通函數一旦調用就會執行完。
// 生成器函數有個特色須要加個*
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是大神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)
複製代碼
// 本地寫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 co let 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是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,它能把任意經過回調函數實現的異步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)})
複製代碼
其實若是看懂到這裏,對於多請求的實現也就不是大難事了。請求能用fetch用fetch哈。
多請求分爲併發請求(請求之間沒有關係,但須要拿到全部請求結果)和串發請求(後面請求必需要拿到前面請求的結果)。
對於併發請求,感受Promise.all
處理更簡單,串發請求那就用await
吧~