async/await和promise.all()

6 篇文章 0 订阅
1 篇文章 0 订阅

一、异步操作解决方法

先来回顾下异步操作:

// 需求:要封装一个方法,提供一个要读取文件的路径,要求这个方法能读取文件,并把内容返回给我

const fs = require('fs')
const path = require('path')

// 这是普通读取文件的方式		第一个参数文件的路径。第二个参数编码
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf-8', (err, dataStr) => {
  	if (err) throw err;
    return dataStr;
})

// 封装方法的初衷: 给定文件路径,返回读取到的内容。而上面我们不可以通过return dataStr将内容返回

​ 当主程序跳到封装好的方法里去执行,但是进入后发现fs.readFile()是个异步方法,主程序不会执行而是放在事件队列中就不管了(让子程序去执行)。然后主程序就跳出这个异步方法,所以就没有return dataStr。所以就拿不到封装好的方法内部的异步函数的返回值。如果用变量接受此方法的返回值就是undefinedreturn不行,我们就用回调函数callback

1. 回调函数callback

// 我们可以规定一下, callback 中,有两个参数,第一个参数,是 失败的结果;第二个参数是 成功的结果;
// 如果路径不合法或者什么导致错误。不thorw掉err而是交给用户处理。所以我们把失败的结果也通过callback交给调用
// 同时,我们规定了:如果成功后,返回的结果,应该位于 callback 参数的第二个位置,此时,第一个位置由于没有出错,所以,放一个 null;如果失败了,则 第一个位置放 Error对象,第二个位置放置一个 undefined
function getFileByPath(fpath, callback) {
  	fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    	// 如果报错了,进入if分支后,if后面的代码就没有必要执行了。所以return
    	if (err) return callback(err);
    	// console.log(dataStr);
    	// return dataStr;
    	callback(null, dataStr);
  	})
}

//调用
getFileByPath(path.join(__dirname, './files/11.txt'), (err, dataStr) => {
  	// console.log(dataStr + '-----')
  	if (err) return console.log(err.message)
    //如果没有失败
  	console.log(dataStr)
})

共用一个callback如果不理解。可以看看下面这种改造的。这种上面callback传一个参数,下面传两个参数的。

// 需求:你要封装一个方法,我给你一个要读取文件的路径,你这个方法能帮我读取文件,并把内容返回给我

const fs = require('fs')
const path = require('path')

//第一个放成功的回调,第二个放失败的回调
function getFileByPath(fpath, succCb, errCb) {
  	fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    	if (err) return errCb(err)
    	succCb(dataStr)
  	})
}

//调用
getFileByPath(path.join(__dirname, './files/11.txt'), function (data) {
  	console.log(data + '成功了!')
}, function (err) {
  	console.log('失败的结果,我们使用失败的回调处理了一下:' + err.message)
})

callback也会有个问题,当回调函数嵌套回调函数时,嵌套多了,会出现回调地狱,将来就会很难维护。

//换一种调用方式
// 需求: 先读取文件1,再读取文件2,最后再读取文件3		这是异步读取,我们来一个异步嵌套就可以了
// 如果层级很深的话 ==>	回调地狱
getFileByPath(path.join(__dirname, './files/1.txt'), function (data) {
  	console.log(data)

  	getFileByPath(path.join(__dirname, './files/2.txt'), function (data) {
    	console.log(data)

    	getFileByPath(path.join(__dirname, './files/3.txt'), function (data) {
      		console.log(data)
    	})
  	})
})

使用 ES6 中的 Promise,来解决 回调地狱的问题。

2. Promise解决回调地狱

Promise 的本质:就是单纯的为了解决回调地狱问题;并不能帮我们减少代码量;

  1. Promise 是一个 构造函数,既然是构造函数, 那么,我们就可以 new Promise() 得到一个 Promise 的实例;

  2. 在 Promise 上,有两个函数,分别叫做 resolve(成功之后的回调函数) 和 reject(失败之后的回调函数);

  3. 在 Promise 构造函数的 Prototype 属性上,有一个 .then() 方法,也就说,只要是 Promise 构造函数创建的实例,都可以访问到 .then() 方法

  4. Promise 表示一个 异步操作;每当我们 new 一个 Promise 的实例,这个实例,就表示一个具体的异步操作;

  5. 既然 Promise 创建的实例,是一个异步操作,那么,这个 异步操作的结果,只能有两种状态:

    5.1 状态1: 异步执行成功了,需要在内部调用 成功的回调函数 resolve 把结果返回给调用者;
    5.2 状态2: 异步执行失败了,需要在内部调用 失败的回调函数 reject 把结果返回给调用者;
    ​5.3 由于 Promise 的实例,是一个异步操作,所以,内部拿到操作的结果后,无法使用 return 把操作的结果返回给调用者; 这时候,只能使用回调函数的形式,来把 成功 或 失败的结果,返回给调用者;

  6. 我们可以在 new 出来的 Promise 实例上,调用 .then() 方法,【预先】 为 这个 Promise 异步操作,指定 成功(resolve) 和 失败(reject) 回调函数;

const fs = require('fs');

var promise = new Promise(function(){
	// 这个 function 内部写的就是具体的异步操作。比如下面这个,就是读文件的异步操作
  	fs.readFile();
})

比如封装读取文件的异步操作的方法

// 初衷: 给路径,返回读取到的内容
function getFileByPath(fpath) {
  //如果不return这个promise。函数内部的变量,外部无法访问(也就是调用时)
  return new Promise(function (resolve, reject) {
    fs.readFile(fpath, 'utf-8', (err, dataStr) => {
		//注意下面的reject和resolve是两个函数。得传递,不能直接调用
      	if (err) return reject(err);
      	resolve(dataStr);
    })
  })
}

//那么调用时就可以通过.then指定resolve和reject
getFileByPath('./files/2.txt').then(function (data) {
	console.log(data);
}, function (err) {
	console.log(err.message);
}) 

Promise执行步骤:

  1. 定义函数

  2. 调用方法

  3. 进入方法new一个Promise实例,并立即执行后面括号中的function.

  4. function中的异步读文件操作还没执行完。就将promise return出去了

  5. 拿到promise实例

  6. 通过.then执行两个回调

  7. function中的异步读文件操作执行完。访问reject()或者resolve()函数

那么对于上面存在回调地狱的读取文件的回调函数嵌套,写法就是下面这样了。可以看到,比起呈阶梯状的回调地狱好很多。

// 读取文件1
getFileByPath('./files/1.txt')
	.then(function (data) {
    	console.log(data)
    	// 读取文件2		在第一个.then()里面return了一个新的promise对象
    	return getFileByPath('./files/2.txt')
  	}, function (err) {
    	console.log('这是失败的结果:' + err.message)
    	// return 一个 新的 Promise
    	return getFileByPath('./files/2.txt')
  	})//第一个.then()后面可以继续.then()。这个.then()操作的就是上一个.then()中返回的promise
  	.then(function (data) {
    	console.log(data)
    	return getFileByPath('./files/3.txt')
  	}, function (err) {
    	console.log('这是失败的结果:' + err.message)
    	return getFileByPath('./files/3.txt')
  	})

如果有多个 promise 需要依次处理, 支持链式编程.then(),前提条件:前一个promise必须返回(return)一个promise对象

promise解决了回调地狱的问题,但是可读性并没有那么高。

二、async/await 和 promise.all()

1.async/await

正常使用promise来进行异步操作

function myFun() {				
    return new Promise((resolve, reject) =>  {
        let data = '123';	    //假设一个耗时的异步操作获取到数据data
        resolve(data);
    }
}
                       
myFun().then((vo)=>{
   //通过.then()使得我们在异步操作完成获取到数据后才进行下面的代码
   console.log(vo);				
})

单一的Promise,它的then链看起来还没什么,如果需要处理由多个 Promise 组成的 then 链的时候,async/await优势就能体现出来了。async和await的使用可以使得异步的代码有了同步的风格。

从字面意思来理解。async 是”异步“的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

function myFun() {				
    return new Promise((resolve, reject) =>  {
        let data = '123';	    //假设一个耗时的异步操作获取到数据data
        resolve(data);
    })
}
                       
//async 会将其后的函数的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来
//await后的函数必须为promise,而如果myFun本身就是promise函数,那么加不加async都行
async function getData() {
	let data = await myFun();
    console.log(data);
}

getData();

Promise通过.then()去解决了回调地狱的问题,而async/await则是进一步优化了then链,使得异步代码有了同步的风格。

2. promise.all()

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

promise.all()可以将多个promise实例包装成一个新的promise实例。可以让多个异步promise并发进行。

它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。此方法在集合多个 promise 的返回结果时很有用。比如说页面需要两个或以上的ajax请求返回数据后才正常显示

function fun1(){
    return new Promise((resolve, reject) => {
      //promise 1 
   })
}

function fun1(){
  return new Promise((resolve, reject) => {
      //promise 2 
   })
}

function getData() {
    Promise.all([fun1, fun2]).then((result) => {
      console.log(result)              
    }).catch((error) => {
      console.log(error)
    })
}


//如果使用async/await
async function getData() {
    await promise.all([fun1, fun2]);
}

​ 成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。Promise.all()获得的成功结果的数组里面的数据顺序和Promise.all()接收到的数组顺序是一致的,即fun1的结果在前,即便fun1的结果获取的比fun2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all()毫无疑问可以解决这个问题。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值