异步:先干一件事 中间去干其余的事,最终在回来干这件事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
吧~