深入理解async函数——ES6篇

含义及作用

引入了 函数,使得异步操作变得async更加方便。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。async函数对 Generator 函数的改进,体现在以下四点。

  • 内置执行器
  • 更好的语义
  • 更广的适用性
  • 返回值是 Promise

基本用法

async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回
等到异步操作完成,再接着执行函数体内后面的语句。

async function getStockPriceByName(name) {
	const symbol = await getStockPriceByName(name);
	const stockPrice = await getStockPriceByName(symbol);
	return stockPrice;
}

getStockPriceByName("goog").then(function(result) {
	console.log(result);
})

async函数返回的promise对象,必须等到内部所有await命令后面的promise对象执行完,才会发生状态变化除非遇到return语句或者抛出错误。也就是说,只有等async函数内部的异步操作对象执行完,才会执行then方法制定的回调函数

async function getTitle(url) {
	let response = await fetch(url);
	let html = await resoponse.text();
	return html.match(/<title>([\s\S] + ) <\/title>/i)[i];
}

getTitle('https://tc39.github.io/ecma262/').then(console.log)

正常情况下,await命令后面是一个Promise对象,返回该对象的结果。如果不是promise对象,就直接返回对应的值
await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于Promise对象

class Sleep{
	constructor(timeout) {
		this.timout = timout;
	}
	then(resolve, reject) {
		const startTime = Date.now();
		setTimeout(
			() => resolve(Date.now() - startTime),
			this.timeout
		);
	}
}

(async () => {
	const.actualTime = await new Sleep(1000);
	console.log(actualTime);
})();

错误处理

使用 try...catch结构,实现多次重复尝试,await后面的promise对象会抛出一个错误对象,导致catch方法的回调函数被配出的错误对象调用。

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
	let i;
	for (i = 0; i < NUM_RETRIES; ++i) {
		try{
			await superagent.get('http://google.com/this-throw-an-error');
			break;
		} catch (err) {

		}
	}
	console.log(i);
}

test();

与其他异步处理方法的比较:

async函数的实现原理:将Gennerator函数和自动执行器,包装在一个函数里

我们通过一个例子,来看 async 函数与 Promise、Generator 函数的比较。

假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。

Promise 的写法

function chainAnimationsPromise(elem, animations) {

	//变量ret用来保存上一个动画的返回值
	let ret = null;

	//新建一个空的promise
	let p = Promise.resolve();

	//使用then方法。添加所有动画
	for(let anim of animations) {
		p = p.then(function(val) {
			ret = val;
			return anim(elem);
		});
	}

	//返回一个部署了错误捕捉机制的Promise
	return p.catch(function(e) {
		//忽略错误,继续执行
	}).then(function() {
		return ret;
	});
}

虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去, 代码完全都是 Promise 的 API(then、catch等等),操作本身的语义反而不容易看出来。

Generator 函数的写法

function chainAnimationsGenerator(elem, animations) {
	return spawn(function*() {
		let ret  = null;
		try{
			for(let anim of animations) {
				ret == yield anim(elem);
			}
		} catch(e) {
			//忽略错误,继续执行
		}
		return ret;
	});
}

上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证yield语句后面的表达式,必须返回一个 Promise。

async 函数的写法:

async function chainAnimationsAsync(elem, animations){
	let ret = null;
	try {
		for(let anim of animations) {
			ret = await anim(elem);
		}
	} catch(e) {
		//忽略错误,继续执行
	}

	return ret;
}

实例

1) 按照顺序完成异步操作,并发执行(节省时间)

async function logInOrder(urls) {
	//并发读取远程URL
	const textPromise = urls.map(async url => {
		const response = await fetch(url);
		return response.text();
	});

	//依次序输出
	for (const textPromise of textPromise) {
		console.log(await textPromise);
	}
}

虽然map方法的参数是async函数,但它是并发执行的,因为只有async函数内部是继发执行, 外部不受影响。后面的for..of循环内部使用了await,因此实现了按顺序输出。

for await...of
for...of 循环用于遍历同步的Iterator接口,新引入的for await...of 循环,则是用于遍历异步的Iterator接口

async function f() {
		for await (const x of createAsyncInterable(['a','b'])) {
			console.log(x);
		}
	}
	//a
	//b

注意:

1)await 命令后面的promise对象,运行结果可能是rejected,所以最好把await命令放在 try...catch 代码块中

async function myFunction() {
	try {
		await somethingThatReturnAPromise();
	} catch(err) {
		console.log(err);
	}
}
//另一种写法
async function myFunction() {
	await somethingThatReturnAPromise()
	.catch(function (err) {
		console.log(err);
	});
}

2 )多个await 命令后面的异步操作,如果不存在继发关系,最好让它们同时出发
继发写法(不推荐):比较耗时,执行完一个才能执行下一个

let foo = await getFoo();
let bar = await getBar();

同时出发(推荐): 缩短程序的执行时间

//写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
//写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3 )await命令只能在async函数之中,如果在普通函数就会报错

async function dbFunc(db) {
	let docs = [{},{},{}];
//报错
	docs.forEach(function (doc) {
		await db.post(doc);
	});
}
//报错,因为await用在普通函数之中了

function dbFunc(db) {//这里奴需要async
	let docs = [{}, {}, {}];

	//可能得到错误结果
	docs.forEach(async function(doc) {
		await db.post(doc);
	});
}
//原因是这时三个db.post操作将是并发执行,也即是同时执行,而不是继发执行
//正确写法是采用for循环
async function dbFunc(db) {
	let docs = [{},{},{}];

	for (let doc of docs) {
		await db.post(doc);
	}
}

猜你喜欢

转载自blog.csdn.net/PINGER0077/article/details/84964647