Promise对象与async和await关键字

Promise

ES6中 Promise出现的目的是解决Node.js异步编程中回调地狱的问题。Promise只是异步编程语法上的改进,并没有提供新的功能。可以让我们将异步API的执行和处理相分离。

Promise本身是一个构造函数,如果需要用Promise,首先需要new一个Promise的实例对象。

Promise构造函数接收一个匿名函数作为参数,这个匿名函数中又有两个参数,分别是resolve,和reject。

Promise想我们把原本的异步API代码放在这个匿名函数当中。比如说里面有一个定时器,定时器执行完之后会有 一个结果出来。Promise不想我们在定时器的回调函数内部处理结果,而是把结果拿到回调函数外面去处理。

resolve参数实际上是一个函数,当异步API有返回结果的时候,我们可以调用resolve这个函数,并且把异步API的结果通过参数的形式传递出去。
reject参数实际上也是一个函数,当异步API执行失败了,我们就可以调用reject这个函数把失败结果传递到Promise外面去。

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '张三'})
        }else {
            reject('失败了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result)) // {name: '张三'})
       .catch(error => console.log(error)); // 失败了)

Promise实际上就是把原本的异步API外面去包裹一层,然后当原本异步API有返回结果的时候,它为我们提供两个方法,成功就调用resolve把结果传递到Promise整体的外面去,失败就调用reject把结果传递到外面去。

在Promise外面怎么拿到原本异步API的结果

我们可以通过创建的Promise的实例对象拿到。

promise里面有一个then方法,这个方法接收一个函数作为参数。我们之前在调用resolve方法的时候实际上就是在调用这个函数。这个函数也称为then方法的回调函数。

promise里面还有一个catch方法,这个方法也接收一个函数作为参数。我们之前在调用reject方法的时候实际上就是在调用这个函数。这个函数也称为catch方法的回调函数。

promise允许我们链式编程,所以在 .then 方法之后可以继续 .catch 调用方法


解决回调地狱

比如先在有一个需求:读取1.txt文件后再读取2.txt文件,最后读取3.txt文件(不能够是同时读取1、2、3文件)
我们要将原本读取文件的异步API分别放到一个Promise里面,然后把这个promise用函数封装起来。当我们要读取文件的时候就调用这个函数就可以,不调用函数就不会读取文件。

const fs = require('fs');
function p1 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./1.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

function p2 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./2.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

function p3 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./3.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

p1().then((r1)=> {
	console.log(r1);
	return p2();
})
.then((r2)=> {
	console.log(r2);
	return p3();
})
.then((r3) => {
	console.log(r3)
})

在当前的then里面return一个promise对象,那么下一个then拿到的结果就是上一个then里面return的promise对象结果。
( 也就是原本链式操作一直返回的都是当前的promise,而我们在then里面return另一个promise,链式的下一个方法就基于return的那个promise来 )

Promise的缺点就是不能取消。
axios可以取消是因为它有cancelToken机制


Promise语法还是比较繁琐的,我们需要在Promise里面写异步API,然后手动调用方法将结果传递出去,接着还要链式使用 .then 来处理结果。这样代码看起来就比较臃肿。
我们需要更好的方式,既可以解决回调地狱又可以使代码看着清晰明了。
→ 引申出ES7中的异步函数语法 →
实际上这个异步函数就是基于Promise对象进行一层封装,它将一些我们看起来比较臃肿的代码封装起来,然后开放一些关键字供我们使用。

异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

所谓的异步函数就是在普通函数前面加上async关键字。
异步函数默认的返回值是promise对象,不是undefined

//语法:
const fn = async () => {};
//或者
async function fn () {}

/*例子------*/
 async function fn () {
 	throw '发生了一些错误';
 	return 123;
 }
// console.log(fn ())
 fn ().then(function (data) {
 	console.log(data);
 }).catch(function (err){
 	console.log(err);
 })
  • async关键字
  1. 普通函数定义前加async关键字 普通函数变成异步函数
  2. 异步函数默认返回promise对象,普通函数默认是返回undefined,异步函数默认是Promise{ undefined }
  3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
  4. 在异步函数内部使用throw关键字抛出程序异常,throw关键字相当于reject,throw执行之后它后面的代码就不会被执行
  5. 调用异步函数再链式调用then方法获取异步函数执行结果(也就是return的内容)
  6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息(也就是throw的内容)
  7. 使用async关键字我们只是省去了Promise对象的创建,其他地方和promise都是一样的

async关键字里面还有另外一个神秘人物 它就是 await关键字

  • await关键字
  1. await关键字只能出现在异步函数中
  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 直到promise返回结果

await关键字使用:我们继续上面依次读取三个文件的例子
我们先将之前的创建Promise对象改写成使用async关键字的写法(这里暂时用一个字符串代替原来的读取文件),然后再写多一个async异步函数来调用上面这三个读取文件的函数。

async function p1 () {
	return 'p1';
}

async function p2 () {
	return 'p2';
}

async function p3 () {
	return 'p3';
}

async function run () {
// p1();p2();p3(); 这种写法就是同时调用这三个函数,所以我们要的不是这种方式,需要在他们的前面都加上await关键字
// p1()返回的是promise对象,正常来说我们是需要调用then来处理结果,也就是 p1().then() 
//但是在异步函数内部可以不这样做,我们可以在promise对象前面加上await关键字,await关键字就会暂停当前函数的执行

// await p1() 也就是说当前这个p1没有返回结果,是不会向下执行的;还有一个特性就是我们可以通过返回值的形式拿到p1执行的结果
// await p2() 
// await p3()
	let r1 = await p1()
	let r2 = await p2()
	let r3 = await p3()
	console.log(r1)
	console.log(r2)
	console.log(r3)
}

run();

接下来,我们将原来的返回字符串改写成真正的读取文件。但是,我们会发现 fs.readFile()方法是在回调函数里面获取返回结果,它不返回一个promise对象,那么我们就不能够在它前面加await关键字。另外它都不是直接返回结果,用async关键字也是得不到结果啊。也就是异步函数没法用了?还是要原来的new一个Promise那样??

于是Node.js为我们提供了一个方法,这个方法可以帮助我们对现有的异步API进行包装,让异步API返回一个promise对象,以支持异步函数语法。Node.js为我们提供的这个方法就叫做 promisify,它存放在 until 模块当中,所以我们使用promisify方法之前需要引入 until 模块

promisify方法
我们将需要改造的异步API作为参数给promisify方法进行处理,注意是传递异步API,不是调用异步API,所以作为promisify的参数时不要加小括号
promisify方法的返回值是一个改造好的新方法。

const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步API 让其返回promise对象
const readFile = promisify(fs.readFile);

async function run () {
	let r1 = await readFile('./1.txt', 'utf8')
	let r2 = await readFile('./2.txt', 'utf8')
	let r3 = await readFile('./3.txt', 'utf8')
	console.log(r1)
	console.log(r2)
	console.log(r3)
}
run();

捕获错误

在node.js中,异步API的错误信息都是通过回调函数获取的,支持Promise对象的异步API发生错误可以通过catch方法捕获。
异步函数执行如果发生错误要如何捕获错误呢?

try catch 可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能其他类型的API发生的错误。

/* 这是在一个服务器端的app.js文件中截取出来的片段 */
app.get("/", async (req, res, next) => {
     try {
         await User.find({name: '张三'})
     }catch(ex) {
         next(ex);
     }
 });

你可能感兴趣的:(JavaScript)