文章回答以下问题:
- 什么是异步,什么是同步
- 回调跟异步的关系
- 如何知道一个函数是异步
- promise是什么,为什么需要他
- promise的初级用法
什么是异步?什么是同步?
程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。
如果能直接拿到结果,就是同步,比如排队买东西,必须人在那里,人拿到结果才走。同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
如果不能直接拿到结果,就是异步,比如拿号排队,排到之前可以干其他事情。你可以每十分钟去询问一下(轮询),也可以扫码微信接收通知,快到时间微信会通知你(回调)。异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。
什么是回调?
写给自己用的函数,不是回调,写给别人用的函数,才是回调。比如request.onreadystatechange函数是给浏览器回头调用的函数(当结束时,如果状态码是4...就执行...),所以他就是一个回调。
回调举例👇
function f1(){}
function f2(fn){}
fn()
}
f2(f1)
//我没有调用f1,我把f1传给了f2,f2调用了f1,f1是我写给f2调用的函数,所以f1是回调,f2不是。
异步和回调的关系
联系
回调是实现异步的一种方式。异步任务需要在得到结果时通知JS来拿结果,如何通知呢?可以让JS留一个函数地址(电话号码)给浏览器,异步任务完成时,浏览器调用该函数地址即可(拨打电话),同时把结果作为参数传给该函数(电话里说可以吃了),这个函数就是我写给浏览器调用的,所以是回调函数。
区别
异步任务需要用到回调函数来通知结果,同时也可以用轮询来通知结果,不是一定要用回调。
回调不一定只在异步里用到,也可以在同步任务里用,比如 array.forEach(n ⇒ console.log(n) 就是同步回调。
如何知道一个函数是同步还是异步
如果一个函数的返回值处于
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener 事件监听
在这三个内部时,那么这个函数就是异步函数
当异步任务有两种结果时
常见方法之一就是搞两个回调,对应成功和失败,比如在AJAX中,可能是这样:
ajax = (method, url, options) => {
const success = options.success
const fail = options.fail
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () =>{
if(request.readyState === 4){
if(request.status < 400){
success.call(null, request.response) //成功
}else if(request.status >= 400){
fail.call(null, request, request.status) //失败
}
}
}
request.send()
}
ajax('get','/xxx',{
success(response){}, fail:(request, status)=>{}
})
//如此封装是有问题的,这里只是举例说明成功和失败的情况
上面这种方法的不足:
- 不规范。名称五花八门,可以是success+error,也可以是success+fail,甚至是done+fail
- 容易出现回调地狱
- 很难进行错误处理
回调地狱举例:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// 中间可能有20层
});
});
});
});
所以出现了promise,来解决以上三个问题
Promise
废话不多说,我们看用了promise之后,之前的例子可以改成:
ajax = (method, url, options) => {
return new Promise((resolve, reject)=>{ //解决回调地狱的精髓,同时统一名称
const success = options.success
const fail = options.fail
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () =>{
if(request.readyState === 4){
if(request.status < 400){
resovle.call(null, request.response)
}else if(request.status >= 400){
reject.call(null, request, request.status)
}
}
}
request.send()
})
}
ajax('get','/xxx').then((response)=>{}, (request, status)=>{})
如此,便解决了一开始说的三个问题
promise初级用法总结
- return new Promise((resolve, reject)=>{...}
- 任务成功则调用resolve(result)
- 任务失败则调用reject(error)
- resolve 和 reject 再去调用成功和失败函数
- 使用.then(success, fail)传入成功和失败函数