异步编程

回调函数

  • 函数也是一种数据类型,可以做参数也可以做返回值,一般情况下,把函数作为参数的目的就是为了获取函数内部的异步操作结果

异步操作

  • 注意:凡是需要得到一个函数的内部异步操作的结果
  • 异步操作类型例如:
    • setTimeout
    • readFile
    • writeFile
    • ajax
    • readdir
  • 这种情况就必须通过回调函数,往往异步的API都伴随有一个回调函数
console.log(1)

setTimeout((function){
    console.log(2)
    console.log('hello')
},0)

console.log(3)
//即使计时器的时间为0或者没有,程序也是按照1,3,2,hello的顺序执行,执行的结果也是1,3,2,hello,不会去等待异步函数setTimeout执行后去执行下一步函数,而是先跳过

函数封装异步操作

function add(x,y){
    console.log(1)
    setTimeout(function(){
        console.log(2)
        var ret = x + y
        return ret
    },1000)
    console.log(3)
    //函数到这里就会结束了,不会等待前面的异步函数定时器setTimeout执行后再去执行下一步函数,所以返回了默认值,执行的顺序就是1,3,执行的结果是1,3,undefined,不会去执行封装里面的函数了,等于直接跳过了,所以最后的才会是未定义undefined
}
console.log(add(10,20)) //undefined

不成立的结果

function add(x,y){
    console.log(1)
    setTimeout(function(){
        console.log(2)
        var ret = x + y
        return ret
    },1000)
    console.log(3)
    return ret   //这里也不会执行封装函数里面的程序的,最后也会是undefined
}
console.log(add(10,20)) //undefined

可以强制执行封装函数的结果,但是没有意义

var ret
function add(x,y){
    console.log(1)
    setTimeout(function(){
        console.log(2)
        var ret = x + y
        return ret
    },1000)
}
add(10,20)

    setTimeout(function(){
        console.log(ret)
    },1000)
//通过定义全局变量,再通过两个定时器setTimeout先执行前面定时器的原则,所以得到了封装函数里面定时器的结果,执行结果就是1,2,30

回调函数

function add(x,y,callback){
    //callback 就是回调函数,callback会触发下面add回调函数的执行,从能够调动里面封装函数计时器的执行,从而得到封装函数的结果
    //var x = 10
    //var y = 20
    //var callback = function (ret){console.log(ret)}
    setTimeout(function(){
      var ret = x + y
      callback(ret)  //这里的ret是实参,这里会触发回调函数的执行,然后把实ret的结果传递给下面形参ret
 },1000)
}


//这里面的ret就是形参
add(10,20,function(ret){
  console.log(ret)  //最后在这里得到最后的结果
})
callback hell
  • 这种回调函数是一种嵌套形式的
//获取第一份数据
$.get(url,(data1)=>{
    console.log(data1)
    
   
     //获取第二份函数
    $.get(url,(data2)=>{
        console.log(data2)
        
        
        //获取第三份数据
        $.get(url,(data3)=>{
           console.log(data3)
          
            //h还可以获取更多的数据
        })
    })
})

promise

  • 这种回调函数是一种油管形式的(异步调用链式编程),就像是一节一节的相连接的,永远是一层的
function getData(url){
    return new Promise((resolve,reject) =>{
        $.ajax({
            url,
            success(data){
                resolve(data)
            },
            error(err){
                reject(err)
            }
        })
    })
}

const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'

getData(url1).then(data1 =>{
    console.log(data1)
    return getData(url2)
}).then(data2 =>{
    console.log(data2)
    return getData(url3)
}).then(data3 =>{
    console.log(data3)
}).catch(err => console.error(err))
promise例子
const url ='https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
const url1 ='https://www.baidu.com/img/flexible/logo/pc/result.png'


function loadImg(src){
    return new Promise((resolve,reject) =>{
        const img =document.createElement('img')
        img.onload = () =>{
            resolve(img)
        }
        img.onerror = () =>{
            const err = new Error(`图片加载失败 ${src}`)
            reject(err)
        }
        img.src = src
    })
}

loadImg(url).then(img =>{
    console.log(img.width)
    return img  //普通对象
}).then(img =>{
    console.log(img.height)
    return loadImg(url1) //promise实例对象
}).then(img1 => {
    console.log(img1.height)
}).catch(ex => console.error(ex))

  • 这样的异步是无法保证文件的加载顺序
var fs requires('fs')

fs.readFile('./data/a.txt',function(err,data){
    if(err){
        //return.console.log('读取失败')
        //抛出异常
        //  1.阻止程序的执行
        //  2.把错误消息打印到控制台
        throw err
    }
    console.log(data)
})

fs.readFile('./data/b.txt',function(err,data){
    if(err){
        throw err
    }
    console.log(data)
})

fs.readFile('./data/c.txt',function(err,data){
    if(err){
        throw err
    }
    console.log(data)
})
//上面三个文件读取的异步代码,通常来说文件大的读取速度较慢,但是文件大的不一定慢,所以运行顺序不一定是从头到尾的顺序,取决操作系统的调度顺序,但是我们无法确定其中的调度顺序
  • 所以有了把回调函数嵌套的方式来保证顺序的方式
var fs requires('fs')

fs.readFile('./data/a.txt',function(err,data){
    if(err){
        //return.console.log('读取失败')
        //抛出异常
        //  1.阻止程序的执行
        //  2.把错误消息打印到控制台
        throw err
    }
    console.log(data)
    fs.readFile('./data/b.txt',function(err,data){
         if(err){
            throw err
         }
         console.log(data)
        fs.readFile('./data/c.txt',function(err,data){
            if(err){
               throw err
            }
            console.log(data)
        })
    })
})
  • 但是回调嵌套的方式,非常杂乱不美观,不利于维护,所以在EcmaScript 6中新增了一个API:promise


    1593424330520.png
  • EcmaScript 6中本身就创建了一个promise类,而我们在这里只不过是创建(new)了一个实例,用实例调用promise中的方法而已
  • resolve(解决),reject(驳回)本身只是个形参,用来接收err和data的状态
  • promise本身不是异步函数,里面的readFile是异步函数
  • .then的意思就是调用new出来的promise函数里面的方法和this调用自己所处这个函数意思差不多,就是相当一个代词,代表它所代表的函数
var fs = require ('fs')

var read =new Promise(function(resolve,reject){
    fs.readFile('./data/a.txt','utf-8',function(err,data){
        if(err){
            //如果promise中的任务失败了,pending的状态会变成Rejected
            //调用reject就相当于调用了then方正的第二个参数函数
            reject(err)
        }else{
            //如果promise中的任务成功了,pending的状态就会变成Resolved
            //这里调用resolve方法实际上就是then方法传递的那个function
            resolve(data)
        }
    }) 
})
    
var read1 =new Promise(function(resolve,reject){
    fs.readFile('./data/b.txt','utf-8',function(err,data){
        if(err){
            reject(err)
        }else{
            resolve(data)
        }
    }) 
}) 
    
 var read2 =new Promise(function(resolve,reject){
    fs.readFile('./data/c.txt','utf-8',function(err,data){
        if(err){
            //如果promise中的任务失败了,pending的状态会变成Rejected
            //调用reject就相当于调用了then方正的第二个参数函数
            reject(err)
        }else{
            //如果promise中的任务成功了,pending的状态就会变成Resolved
            //这里调用resolve方法实际上就是then方法传递的那个function
            resolve(data)
        }
    }) 
})

//then接收的function(data)就是容器中的resolve函数,经过上面判断文件读取是否成功,还接收了传过来的文件数据   //then接收的function(err)就是容器中的reject函数,经过上面判断文件读取是否成功,如果不成功就会回来调用'文件读取失败'的函数信息
read
    .then(function(data){
    console.log(data)
    //当read读取成功的时候
    //当前函数中return的结果就可以在后面的then中function(data)接收到
    //当你return 123,后面的.then接收的就是123,也就是说前面的return返回啥,后面收到的就是啥,没有return就是undefined
    //当然return如果返回的是promise早已赋值好的read对象,后面.then接收的也是个对象
    //当return一个read也就是promise对象的时候,后面的.then中的,方法的第一个参数将会作为read的resolve
    
    return read1
},function(err){
    console.log('文件读取失败',err)
})
   .then(function(data){
     console.log(data)
    return read2
},function(err){
    console.log('文件读取失败',err)
})
  .then(function(data){
    console.log(data)
    console.log('end')
},function(err){
    console.log('文件读取失败',err)
})

异步调用链式函数封装

var fs = require ('fs')

function pReadFile(filePath){
   return new Promise(function(resolve,reject){
    fs.readFile(filePath,'utf-8',function(err,data){
        if(err){
            reject(err)
        }else{
            resolve(data)
        }
    }) 
})  
} 

pReadFile('./data/a.txt')
    .then(function(data){
     console.log(data)
    return pReadFile('./data/b.txt')
},function(err){
    console.log('文件读取失败',err)
})
    .then(function(data){
     console.log(data)
    return pReadFile('./data/c.txt')
},function(err){
    console.log('文件读取失败',err)
})
    .then(function(data){
     console.log(data)
     console.log('end')
},function(err){
    console.log('文件读取失败',err)
})

//reject是个可选参数,也可以写,还可以简写
const file1 = './data/a.txt'
const file2 = './data/b.txt'
const file3 = './data/c.txt'

pReadFile(file1).then(data =>{
    console.log(file1)
    return pReadFile(data) 
    throw err
}).then(data =>{
    console.log(data)
    return pReadFile(file3) 
    throw err
}).then(data => {
    console.log(data)
    throw err
})



pReadFile(file1).then(data =>{
    console.log(file1)
    return pReadFile(data) 
}).then(data =>{
    console.log(data)
    return pReadFile(file3) 
}).then(data => {
    console.log(data)
}).catch(err => console.error(err)) //这里会抓取前面的错误

async函数把异步操作变成同步

​ async函数可以加在任何函数前面,,被加在async函数的函数会类似异步函数的操作,而异步函数会变成同步函数(这种现象就是,在加载的时候,不会像同步函数一样严格的按照代码顺序执行,被加async函数的函数有像异步函数一样有时候快有时候慢,被加async函数的异步函数同时会想同步一样执行,主要是看加载速度,速度可以的话,会按照代码顺序执行)

//Promise 还是不太方便,还是需要回调函数
//async函数的本质就是生成器函数的语法糖,所以对于开发人员来讲,已经不需要关心生成器函数,原先Genarator函数需要next一步一步的调用,后来有人搞了第三方库co(生成器函数),最后官方发布了官方生成器函数async函数
//   * 换成了 async,yield换成了await,这样的话更语义了
//node LTS版本都支持async函数,babel也支持编译转换async函数

//在新的版本中Ecmascript 中增加了一个函数:async函数

//语法

//async function add(x,y) {
    //在async函数内部可以使用await的方式来操作异步API
  //return x + y
    //let a = await 1
    //let b = await 2
    //let c = await 'hello'

    //await是一种新的语法,只能在async函数中使用
    //await一般是在其后面跟一个 promise 操作API
//}

//let ret = add(10,20)

//console.log(ret)

//callback的方式
const fs = require('fs')

/*fs.readFile('./a.txt','utf-8',function (err,data) {
 if (err){
     throw err
 }
 console.log(data)
})
fs.readFile('./b.txt','utf-8',function (err,data) {
 if (err){
     throw err
 }
 console.log(data)
})*/



//Promise的方式
/*
function readFile(...args) {
  return new Promise((resolve,reject) =>{
      //出现在方法定义参数中叫做:rest参数,出现在方法调用的参数中叫做:数组的展开操作符
      //...args 数组的展开操作符.
      // fs.readFile.apply(null,args)
      fs.readFile(...args,(err,data) =>{
          if (err){
              return reject(err)
          }
          resolve(data)
      })
  })
}

readFile('./a.txt','UTF-8').then(data =>{
    console.log(data)
    return readFile('./b.txt','UTF-8')
})
.then(data =>{
    console.log(data)
    return readFile('./c.txt','UTF-8')
})*/



//async方式,任何函数前面加上了async就变为了一个async函数(类似于异步函数,真正的异步函数只要异步操作才是异步函数)
//只有在async 函数中才可以使用await结合Promise来使用同步的风格来调用异步API,让异步执行像同步一样执行,但本质上是同步的
//async函数的作用就是让你的异步代码写起来像同步代码一样
let util = require('util')

let readFile = util.promisify(fs.readFile)

readFile('./a.txt','utf-8').then(data =>{
    console.log(data)
})
readFile('./b.txt','utf-8').then(data =>{
    console.log(data)
})
readFile('./c.txt','utf-8').then(data =>{
    console.log(data)
})

//函数中的await就像是叫其他函数执行等它一下的样子,这样的函数写出来和同步一样,异步加载不同加载的时候,速度不一样!
async function read() {
  console.log(1)
    //async函数中如果遇到await异步操作,则方法会等待,而我们的程序不会等待
  const dataA = await readFile('./a.txt','utf-8')
  const dataB = await readFile('./b.txt','utf-8')
  const dataC = await readFile('./c.txt','utf-8')
      //这里面的三个函数是串行的
  console.log(dataA,dataB,dataC)
  console.log(2)
    //async函数返回的是一个Promise对象,你需要通过.then的方式来接收async函数中的返回值
    return [dataA,dataB,dataC]
}
console.log(3)
read()
read().then(data =>{
    console.log(data)
})
console.log(4)

//在async函数中调用另一个async函数
async function fn() {
  const data = await read()
      console.log(data)
}
fn()
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容