synchronous同步
如果在函数返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。如果能直接拿到结果,就是同步
举例子:在餐厅排队吃饭,必须要吃到饭你才会离开餐厅,这个等待的过程可能消耗很久也可能很短,总之吃不到饭是不会离开的
asynchronous异步
如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。如果不能直接拿到结果,就是异步
举例子:在餐厅排队吃饭,可以取号,在等待的过程中,你可以离开餐厅,每10分钟取餐厅询问一次,称为轮询,扫码微信得到就餐通知,称为回调。
callback回调定义
回调函数是异步操作最基本的方法。简单来说,回调就是自己写了却不调用,给别人调用的函数
举例:以AJAX为例,request.send()调用之后,并不能直接得到response,必须等到readyState变为4后,浏览器才开始回头调用request.onreadystatechange(), 最终我们才得到reques.reponse。
代码实例
代码1:函数2调用函数1
f1函数被声明,却没有被调用- 把
f1函数当成参数传给f2函数, f2函数调用了f1函数,所以f1是回调
function f1(){}
function f2(fn){
fn()
}
f2(f1)
代码2:
fn('你好')中的fn就是f1, fn('你好')中的你好会被赋值给参数x
function f1(x){
console.log(x)
}
function f2(fn){
fn('你好')
}
f2(f1)
异步与回调的关系
关联
异步任务需要在得到结果时通知JS来读取结果。等到通知的过程就是异步,通知JS读取结果的过程就是回调。具体实现就是让JS编写留下函数地址给浏览器(留下电话号码),异步完成后浏览器调用该函数地址(拨打电话),同时把参数传给该函数(打电话通知顾客取那桌就餐),这个函数是用户写给浏览器调用的,所以是回调函数
区别
- 异步任务需要用到回调函数来通知结果
- 回调函数却不一定存在于异步任务中,同步任务中也可以用到回调函数,例如
array.forEach(n=>{console.log(n)}),其中n=>{console.log(n)}就是回调函数
判断同步异步
异步
通常来说,如果当一个函数的返回值处于以下三种情况就是异步函数(还有其他情况,这里暂不说明)
setTimeoutAJAX(XMLHttpRequest)AddEventListener
AJAX也可以设置为同步的,例如在request.open("get", "/style.css", false)添加false。但是在请求期间会让页面卡住,阻止用户其他操作。因此, 异步才是最佳选择。
代码实例
- 代码1
摇骰子()没有写return,所以返回return undefined。箭头函数返回真正的结果
function 摇骰子(){
// 异步函数
setTimeout(()=>{
return parseInt(Math.random()*6)+1
},1000)
//return undefined
}
const n = 摇骰子()
console.log(n) // undefined
- 代码2
如果拿到异步的结果?使用回调。首先声明函数,然后把函数地址传给摇骰子(), 然后摇骰子()得到结果后把结果作为参数传给f1()
function f1(x){console.log(x)}
function 摇骰子(fn){
// 异步函数
setTimeout(()=>{
fn(parseInt(Math.random()*6)+1)
},1000)
//return undefined
}
摇骰子(f1)
可以简化为。但是请注意,如果参数个数不一致,就不能这样简化。例如,函数中一个参数x和一个执行参数x是一致的,简化成功。
摇骰子( x=>{console.log(x)}) // 再简化
摇骰子(console.log)
简化失败的情况,参数个数不一致
// 输入错误结果,简化失败
const array = ['1','2','3']
array.map(parseInt) // [1, NaN, NaN]
// 正确运行
const array = ['1','2','3']
array.map((item, i, array) =>{
return parseInt(item)
}
总结
- 异步任务不能拿到结果,所以需要传一个回调给异步任务
- 异步任务完成时调用回调,调用的时候把结果作为参数传给回调函数
异步与回调地狱
如果异步任务有两个结果,分别为成功或失败,怎么办?
尝试
- 方法1: 回调接受两个参数
fs.readFile('./1.txt', (error, data)=>{
if(error){ console.log('失败'); return} // 失败
console.log(data.toString()) // 成功
}
- 方法2:设置两个回调
// 设置失败回调和成功回调
ajax('get', '/1.json', (data => {}), (error=>{}))
// 设置一个对象,根据对象的key值,来执行失败回调和成功回调
ajax('get', '/1.json', { success: ()=>{}, fail:()=>{} )
问题
上述的方法,各自有各自的问题
- 不规范,有人用
Sucess + error, 有人用success + fail, 有人用done + fail - 容易出现回调地狱,代码让人看不懂
- 很难进行错误处理
回调地狱
在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。比如说你要把一个函数A 作为回调函数,但是该函数又接受一个函数B作为参数,甚至 B还接受C作为参数使用,就这样层层嵌套,人称之为回调地狱,代码阅读性非常差。比如:
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
//输出: first second third end
回调的相关问题
- 如何规范回调的名字或者顺序
- 如何拒绝回调地狱,让代码的可读性更强
- 如何更方便的捕获错误
Promise定义
Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等
Promise的三种状态
Pending----Promise对象实例创建时候的初始状态Fulfilled----可以理解为成功的状态Rejected----可以理解为失败的状态
这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,比如说一旦状态变为resolved 后,就不能再次改变为Fulfilled
Promise的语法
return new Promise((resolve, reject)=>{})- 任务成功则调用
resolve(result) - 任务失败则调用
reject(error) resolve和reject会再去调用成功和失败函数- 使用
.then(success, fail)传入成功和失败函数
Promise代码实例
ajax = (method, url, options) =>{
return new Promise((resolve, reject)=>{
const {success, fail} = options
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () =>{
if(request.readyState === 4){
if(request.status < 400){
resolve.call(null, request.response)
}else if(request.status >= 400){
reject.call(null, request)
}
}
}
request.send()
})
}
ajax('get', '/xxx')
.then((reponse)=>{}, (request, status)=>{})
promise的链式调用
- 每次调用返回的都是一个新的
Promise实例(这就是then可用链式调用的原因) - 如果
then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调 - 如果
then中出现异常,会走下一个then的失败回调 - 在
then中使用了return,那么return的值会被Promise.resolve() then中可以不传递参数,如果不传递会透到下一个thencatch会捕获到没有捕获的异常
以上代码的缺点
- 无法上传数据,因为
send()这里没有使用POST方法, 上传数据 - 无法设置请求头,没有使用
request.setRequestHeader(key, value)
AJAX库
jQuery.ajax是目前比较完美的AJAX库
- 支持更多形式的参数
- 支持
Promise - 支持超多功能
axios是目前最新的AJAX库
JSON自动处理:axios发现响应的Cotent-Type是json,就会自动调用JSON.parse- 请求拦截器: 用户可以在所有请求里加些东西,比如查询参数
- 响应拦截器: 用户可以自定义所有响应,修改内容
- 可以生成不同对象:不同的实例,可以设置不同的配置,用于复杂场景