异步编程在JavaScript中非常重要,但是过多的异步编程同时也带来了回调嵌套的问题。
什么是回调函数?
ajax(url, () => {});复制代码
以上代码就是一个回调函数。一个函数作为参数需要依赖另一个函数执行调用。
但是回调函数有一个致命弱点,容易出现回调地狱(Callback hell)
什么是回调地狱?
let form = document.querySelector('form');
form.onsubmit = function (e) {
var name = document.querySelector('input').value;
$.ajax({
url: "http://demo.com/submit",
type:'POST',
data: {name: name},
success: function(res) {
if (res.code === 2000) {
var h1 = document.querySelector('h1').innerHTML;
h1.innerHTML = res.data.name;
}
}
});
}复制代码
像这样,函数作为参数一层层的嵌套,使得代码块看起来庞大、不清晰,不能一下子分清结构层级,这就称为“回调地狱”。
回调地狱的根本问题与缺点:
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
- 嵌套函数一多,就很难处理错误
- 回调函数態使用try...catch...捕获错误,不能直接return
怎么解决回调地狱?
主要原因是因为开发者的编码习惯导致,我们可以通过以下方式来解决:
- 保持简短的代码风格,尽量使用命名函数,避免使用匿名函数
document.querySelector('form').onsubmit = onFormSubmit();
function onFormSubmit(e) {
var name = document.querySelector('input').value;
$.ajax({
url: "http://demo.com/submit",
type:'POST',
data: {name: name},
success: onSuccess(res)
});
}
function onSuccess(res){
if (res.code === 2000) {
var h1 = document.querySelector('h1').innerHTML;
h1.innerHTML = res.data.name;
}
}复制代码
- 模块化,拆分每一个独立的功能函数,封装、打包成一个单独的js文件,通过import导入
// formHandler.js
module.exports.formSubmit = onFormSubmit;
function onFormSubmit(e) {
var name = document.querySelector('input').value;
$.ajax({
url: "http://demo.com/submit",
type:'POST',
data: {name: name},
success: onSuccess(res)
});
}
function onSuccess(res){
if (res.code === 2000) {
var h1 = document.querySelector('h1').innerHTML;
h1.innerHTML = res.data.name;
}
}
// index.js
var formHandler = require('./formHandler');
document.querySelector('form').onsubmit = formHandler.formSubmit;复制代码
- 处理每一个错误,按照标准规范编码
- Promise/Gengenerator/Async Function
除了常见的一种回调函数作为异步处理,还有promises,Generators,async是处理异步处理的方式
Promise的特点是什么?
Promise译为承诺,承诺在以后、未来会有一个确切的回复,并且该承诺拥有三种状态:- 等待中(pending)
- 完成了(resolved)
- 拒绝了(rejected)
这个承诺一旦状态变更了以后,就不能再更改状态了
当我们在构造Promise的时候,构造函数内部的代码是立即执行的
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finish');
// new Promise
// finish复制代码
Promise有什么缺点?
无法取消Promise,错误需要通过回调函数来捕获
什么是Promise链?Promise构造函数执行和then函数执行有什么区别?
Promise
实现了链式调用,也就是说每次调用 then
之后返回的都是一个 Promise
,并且是一个全新的 Promise
,原因也是因为状态不可变。如果你在 then
中使用了return
,那么 return
的值会被 Promise.reslove()
包装
Promise.resovle(1)
.then(res => {
console.log(res); // 1
return 2; // 包装成了 Promise.reslove(2)
})
.then(res => {
console.log(res); // 2
})
复制代码
Promise也解决了地狱回调的问题:
// old
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
// new
ajax(url)
.then(res => {
console.log(res);
return ajax(url1);
})
.then(res => {
console.log(res);
return ajax(url2);
})
.then(res => console.log(res));
复制代码
async和await
await 的同步只是在 async 函数里同步,但并不会阻塞其他外部代码的执行。而这也是 await 必须放在 async 函数里的原因
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。