Skip to content

JS 中的异步

异步的发展史

  1. 回调
  2. 事件监听
  3. 订阅/发布
  4. Promise
  5. Generator
  6. async、await

类型

  1. 回调
  2. 事件监听
  3. 订阅/发布
  4. Promise 对象

1. 回调函数

将回调任务单独写在一个函数内,然后在任务重新执行到此处的时候,直接调用此任务。

js
fs.readFile(fileA, 'utf-8', function(err, data) {
	fs.readFile(fileB, 'utf-8', function(err, data) {
		//.....
	});
});

缺点

  1. 异常处理
js
try {
	fs.readFile(fileA, 'utf-8', function(err, data) {
		fs.readFile(fileB, 'utf-8', function(err, data) {
			//.....
		});
	});
} catch (e) {
	console.log(e);
}

因为回调函数被存放到事件循环队列中去了,直到下一个循环才会执行,但是 try{}catch(e){}只捕获当前循环中的异常,所以对于回调函数没有办法

  1. 无法获取原来的上下文环境数据

  2. 多重异步嵌套形成回调函数地狱

2. 事件监听

采用事件驱动模式

任务的执行不是按照代码的执行顺序而是取决于某一个事件的发生,如 JS 中的事件函数 attachEvent 和 addEvenListener 、onclick 等、observe 、jQuery 的(on、bind、trigger)

  1. attachEvent 和 addEvenListener

详情请看:

  1. onclick

3. 订阅/发布模式

发布订阅模式

js
let fs = require('fs');
let EventEmitter = require('events');

let events = new EventEmitter();

events.on('ready', function(name, value) {
	if(name === 'filea'){ 
		// ...
	}
	if(name === 'fileb'){
		// ...
	}
});

fs.readFile(fileA, 'utf-8', function(err, data) {
	events.emit('filea',data)

});
fs.readFile(fileB, 'utf-8', function(err, data) {
		events.emit('fileb',data)
});

4. Promise 对象

promise 就是为了改进回调函数地狱这个问题而提出的。其通过 then 将多个异步变得更清晰。

Promise 是一个承诺,只可能是成功、失败、无响应三种情况之一,一旦决策,无法修改结果。

Promise 不属于流程控制,但流程控制可以用多个 Promise 组合实现,因此它的职责很单一,就是对一个决议的承诺。

resolve 表明通过的决议,reject 表明拒绝的决议,如果决议通过,then 函数的第一个回调会立即插入 microtask 队列,异步立即执行。

优点

从发展史上看异步(异常处理)

1. 回调

将任务的第二段单独写在一个函数里面,然后在任务重新执行到此处的时候,直接调用此函数。

其具有一下两个缺点:

  1. 无法获取原来的上下文环境数据。

  2. 多重异步嵌套形成回调函数地狱。在出现多重嵌套的时候,代码不是纵向发展,而是横向发展, 且多个异步形成强耦合,如果修改中间的一个,他的上层回调和下层回调可能都需要跟着修改。

  3. 异常无法处理

1. 例子 1
js
function fetch(callback) {
	setTimeout(() => {
		throw Error('请求失败');
	});
}

try {
	fetch(() => {
		console.log('请求处理'); // 永远不会执行
	});
} catch (error) {
	console.log('触发异常', error); // 永远不会执行
}
js
function fetch(callback) {
	try {
		setTimeout(() => {
			throw Error('请求失败');
		});
	} catch (error) {
		console.log('触发异常', error); // 永远不会执行
	}
}

fetch(() => {
	console.log('请求处理'); // 永远不会执行
});

我们看执行结果

异常

我们发现并没有执行 try{}catch(){}而是直接终止了代码的执行,那么说明按照我们正常的使用方式是不可以在函数外部去捕获回调函数中的异常,对于函数内部回调的异常只能通过

js
function fetch(callback) {
	setTimeout(() => {
		try {
			throw Error('请求失败');
		} catch (error) {
			console.log('触发异常', error); // 永远不会执行
		}
	});
}

fetch(() => {
	console.log('请求处理'); // 永远不会执行
});

而上面的这种是不符合我们通常的逻辑的,那么在 Promise 中对于异常时如何处理的

为了解决上面两个问题提出了 Promise

2. Promise

promise 就是为了改进回调函数地狱这个问题而提出的。其通过 then 将多个异步变得更清晰。

Promise 是一个承诺,只可能是成功、失败、无响应三种情况之一,一旦决策,无法修改结果。

Promise 不属于流程控制,但流程控制可以用多个 Promise 组合实现,因此它的职责很单一,就是对一个决议的承诺。

resolve 表明通过的决议,reject 表明拒绝的决议,如果决议通过,then 函数的第一个回调会立即插入 microtask 队列,异步立即执行。

js
//promise
var p = new Promise(function(resolve, reject) {
	//在这里进行处理。也许可以使用ajax
	setTimeout(function() {
		var result = 10 * 5;
		if (result === 50) {
			resolve(50);
		} else {
			reject(new Error('Bad Math'));
		}
	}, 1000);
});
p.then(function(result) {
	console.log('Resolve with a values of %d', result);
});
p.catch(function() {
	console.error('Something went wrong');
});

那么对于 Promise 中异常我们是如何处理的

我们继续看上面的代码我们知道在 Promise 中我们对于错误可以通过 reject 去手动拒绝,然后在 catch 中捕获

js
//promise
var p = new Promise(function(resolve, reject) {
	//在这里进行处理。也许可以使用ajax
	setTimeout(function() {
		var result = 10 * 6;
		new Error('reject之前的异常');
		if (result === 50) {
			resolve(50);
		} else {
			reject(new Error('Bad Math'));
		}
	}, 1000);
});
p.then(function(result) {
	console.log('Resolve with a values of %d', result);
});
p.catch(function(err) {
	console.error('Something went wrong', err);
	console.log('异常下一步');
});

异常

js
//promise
var p = new Promise(function(resolve, reject) {
	//在这里进行处理。也许可以使用ajax
	setTimeout(function() {
		var result = 10 * 6;
		new Error('reject之前的异常');
		console.log(`reject之前的异常之后运行的`);
		if (result === 50) {
			resolve(50);
		} else {
			// reject(new Error('Bad Math'));
		}
	}, 1000);
});
p.then(function(result) {
	console.log('Resolve with a values of %d', result);
});
p.catch(function(err) {
	console.error('Something went wrong', err);
	console.log('异常下一步');
});

异常

js
//promise
var p = new Promise(function(resolve, reject) {
	new Error('reject之前的异常');
	console.log(111);
	reject(new Error('Bad Math'));
});
p.then(function(result) {
	console.log('Resolve with a values of %d', result);
});
p.catch(function(err) {
	console.error('Something went wrong', err);
	console.log('异常下一步');
});

异常

从上面我们可以发现 Promise 捕获了运行中的异常,但是需要我们去通过 reject 去抛出异常

参考:

Javascript 异常处理的演进