大家在js编程中肯定遇到过异步问题,实践中大家在异步问题上也不断提出新的解决方案,这里就梳理一下异步解决方案。
回调函数
回调函数是最原始的解决方案,逻辑上很容易理解。看个例子:
function f1(value, callback) {
console.log('f1');
setTimeout(
() => {
value = setValue('f2');
callback.call(this, value);
}
, 1000
);
}
function f2(value){
console.log(value);
}
function setValue(value) {
return value;
}
let value;
f1(value, f2);
结果.png
这里的
f2
需要f1
延时1s完成value
的重新赋值后再打印value
,比较简单的处理就是把f2
作为f1
的回调函数。
这是历史最悠久的方案
- 优点:兼容性好
- 缺点:当回调函数不断嵌套时,代码会横向发展,形成“意大利面”(Italian noodles)代码
Promise对象
随着历史的发展,在前端社区的某些库中有人提出了Promise
方案,后来这个方案也被写入官方标准。本文不再谈各种前端库中Promise
的实现,只谈目前ES规范
中的Promise
。
几个要点:
-
Promise
是一个原生对象,我们使用Promise
对象是利用它的API。 -
Promise
对象的异步操作有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。回调函数只在fulfilled
(已成功)和rejected
(已失败)状态下触发。 -
Promise
对象的实例可以调用then
方法指定不同状态的回调函数(或catch
方法注册rejected
(已失败)状态的回调函数)
使用Promise
对象重新实现上文的例子:
let value;
function f1() {
console.log('f1');
const promise = new Promise((resolve, reject) => {
setTimeout(
() => {
let value = setValue('f2');
if (!!value) {
resolve(value);
} else {
reject('error');
}
}
, 1000);
});
return promise;
}
function f2(value){
console.log(value);
}
function setValue(value) {
return value;
}
f1()
.then(
f2,
(error) => console.log(error)
);
结果.png
可以看到函数
f1
返回了一个Promise
对象的实例,这个实例就具有.then/.catch
等方法注册回调函数。再看一个使用
Promise
实现的AJAX
封装
function fetchRemote(url, method='GET', param=null) {
const promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4){
return;
}
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
};
xhr.send(param);
});
return promise;
}
// 调用
fetchRemote(url)
.then(
(response) => {
todo...
}
)
.catch(
(error) => {
todo...
}
)
这里提一个小细节: 请注意XMLHttpRequest对象的实例具有实例方法onreadystatechange()
,实际上这是一个注册回调函数的api,每一次XMLHttpRequest实例对象的readyState/status
被改变时,回调函数都会被异步触发
此外,Promise
对象也提供了一些新API适应不同场景,例如.race/.all
。
// Promise.race() 适用于等待多个promise第一个完成的场景
Promise.race(task1, task2, task3...).then().catch();
// Promise.all() 适用于等待多个promise同时完成的场景
Promise.all(task1, task2, task3...).then().catch();
Generator函数
学习过Generator函数
的同学应该知道,Generator函数
最大的特点就是不会顺序执行到底,遇到yield
语句时会暂停,直到调用next
方法才会继续执行。显然,这种特性很适合异步场景。
还是实现最初的小例子:
const setValue = value => value;
let value;
function* f1() {
console.log('f1');
yield new Promise((resolve, reject) => {
setTimeout(() => {
value = setValue('f2');
if (!!value) {
resolve(value);
} else {
reject('error');
}
}, 1000);
});
}
function f2 (value) {
console.log(value);
}
let g = f1();
g.next().value
.then(f2)
.catch((error) => {console.error(error);});
结果.png
再看一个远程请求的例子:
function* fetchData(url) {
const data = yield fetch(url);
yield handler(data);
}
const g = fetchData('http://url/api');
g.next();
g.next();
可以看到,通过使用Generator函数
的属性,异步逻辑变得非常简单,只需要在异步等待的函数处“暂停”即可。这种语法在redux-saga
的库中有很好的应用。
async函数
async函数
顾名思义,就是为解决异步场景而生的。
async函数
返回的是一个Promise
对象,所以async函数
返回的结果可以调用.then()
/.catch()
方法,本质上和promise对象
用法类似,只是有了语法糖书写更简便。
还是这个熟悉的老例子:
const setValue = value => value;
let value;
function f1() {
console.log('f1');
return new Promise((resolve, reject) => {
setTimeout(() => {
value = setValue(null);
if (!!value) {
resolve(value);
} else {
reject('error');
}
}, 1000);
});
}
function f2 (value) {
console.log(value);
}
async function fn() {
return await f1();
}
fn()
.then(
(value) => {console.log(value);}
)
.catch(
(error) => console.error(error)
);
结果.png
简单解释一下,
async
和await
需要互相配合,await
只能在async函数
中使用,await
等的是promise
对象,如果await
得到的不是promise
对象则会把普通对象直接作为promise
的value
并更新状态为resolved
。可以很明显看出,一般情况下
async函数
不具有优势,它的优势在处理then
链。看个例子:
async function manycbs() {
const arg1 = 'xxx';
const arg2 = await step1(arg1);
const arg3 = await step2(arg1, arg2);
const result = await step3(arg1, arg2, arg3);
console.log(result);
}
manycbs();
这样就可以看出async函数
可以把异步写得像同步一样。
网友评论