異步和同步
(混淆了異步和回調)
如果能直接拿到結果,即為同步
例如:在醫(yī)院掛號,拿到號碼才離開窗口
同步任務可能消耗10毫秒,也可能需要3秒
沒有拿到結果就不會離開
如果不能拿到結果,即為異步
例如:去餐廳拿號,之后可以去逛街
每十分鐘去詢問次餐廳,即為輪詢
用微信接收通知,即為回調
以AJAX為例
request.send()之后,并不能直接得到response
必須等到readyState變?yōu)?之后,瀏覽器回調request.onreadystatechange函數(shù),從才能得到request.response,這與餐廳微信類似
回調callback
寫給自己用的函數(shù)不是回調
寫給別人用的函數(shù)就是回調
request.onreadystatechange就是寫給瀏覽器調用的,瀏覽器回頭調用此函數(shù)
回調:回頭將來調用
回調舉例
function f1(){}
function f2(fn){
fn()
}
f2(f1)
把函數(shù)1給另一個函數(shù)2
調用了f1沒有?
答:沒有調用
把f1傳給f2(別人)沒有?
答:傳了
f2調用了f1沒有?
答:f2調用了f1
那么f1就是寫給f2回頭調用的函數(shù),所以f1是回調
異步和回調的關系
關聯(lián)
異步任務需要在得到結果時通知JS來拿結果
通知的方法
可以讓JS寫一個函數(shù)地址給瀏覽器,異步任務完成時瀏覽器調用該函數(shù)地址,同時把結果作為參數(shù)傳給該函數(shù)
由于此函數(shù)是寫給瀏覽器調用的,所以是回調函數(shù)
區(qū)別
異步任務需要用到回調函數(shù)來通知結果
但是回調函數(shù)不一定只用在異步任務里,回調可以用到同步任務里
array.forEach(n => console.log(n))就是同步回調
判斷同步異步
處于這三種內部,即為異步函數(shù)
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener
勿將AJAX設置為同步,這樣做會使請求期間頁面卡住
還有其他異步API,再另行說明
例如:
function 搖骰子(){
setTimeout(()=>{
return parseInt(Math.random()*6)+1
},1000)
}
搖骰子()里面沒有寫return,那就是return undefined
箭頭函數(shù)里有return,返回真正的結果
所以這是一個異步任務(函數(shù))
function 搖骰子(){
setTimeout(()=>{
return parseInt(Math.random()*6)+1
},1000)
}
const n = 搖骰子
console.log(n) //undefined
如何才能拿到異步的結果?
答:可以用回調函數(shù)
function 搖骰子(fn){
setTimeout(()=>{
fn(parseInt(Math.random()*6)+1)
},1000)
}
//function f1(x){ console.log(x) }
//搖骰子(f1)
搖骰子(console.log)
如果參數(shù)不一致就不能簡化
錯誤簡化的例子(面試題)
const a = ['1','2','3'].map(parseInt)
console.log(a)
得到的結果為
['1',NaN,NaN]
非期望結果['1','2','3']
正常非簡化應該是
const a = ['1','2','3'].map((item,i,array)=>{
return parseInt(item)
})
console.log(a)
而錯誤簡化就變成了
const a = ['1','2','3'].map((item,i,array)=>{
return parseInt(item,i,array)
})
console.log(a)
所以正確的簡化應該存在箭頭函數(shù)
const a = ['1','2','3'].map(item=>parseInt(item))
console.log(a)
總結
- 異步任務不能拿到結果
- 于是我們傳一個回調給異步任務
- 異步任務完成時調用回調
- 調用的時候把結果作為參數(shù)
思考:如果異步任務有兩個結果,成功或失敗,應該怎么辦?
方法一:回調接受兩個參數(shù)
node.js的API都是接受兩個參數(shù)的
fs.readFile('./readme.txt', (error, data)=>{
if(error){ console.log('失敗'); return}
console.log( data.toString()) //此為成功
})
方法二:使用兩個回調
ajax('get', './1.json', data=>(){}, error=>(){})
// 前面函數(shù)是成功回調,后面是失敗,或者以下
ajax('get', './1.json', {
success: ()=>{}, fail: ()=>{}
})
//接受一個對象,對象有兩個key表示成功和失敗
然而,這些方法均有些問題
- 不規(guī)范,名稱五花八門,有人用success+error,有人用success+fail,有人用done+fail
- 容易出現(xiàn) 回調地獄,代碼變得難懂
- 很難進行錯誤處理
回調地獄舉例
getUser( user => {
getGroup( user, (groups)=>{
groups.forEach( (g)=>{
g.filter( x=> x.ownerId === user.id)
.forEach( x=> console.log(x))
})
})
})
這僅僅才是四層回調,你可以想象四十層回調?
那么有什么辦法解決這三個問題
- 規(guī)范回調的名字和順序
- 拒絕回調地獄,讓代碼可讀性更強
- 很方便的捕獲錯誤
于是產生了promise思想
例如:AJAX封裝
ajax = (method, url, options) => {
const {success, fail} = options //析構賦值
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status < 400) { //成功就調用success,失敗就調用fail
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)=>{}
})//左邊是function縮寫,右邊是箭頭函數(shù)
現(xiàn)在改為更好的Promise封裝:
//首先更改調用
ajax('get', '/xxx')
.then((response)=>{}, (request)=>{})
//雖然也是回調,但是并不需要success和fail
//then的第一個參數(shù)就是success,第二個參數(shù)為fail
如何得到 .then() 的對象,接下來改造ajax源碼
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) //成功就調用resolve,失敗就調用request
}
}
}
request.send()
}
)
}
return new Promise((resolve, reject) =>()關鍵改造(背下來)
- 第一步:return new Promise((resolve, reject) =>(),成功就調用resolve,失敗就調用request
- 第二部:使用 .then(success,fail)傳入成功和失敗函數(shù)
后面還有更高級的Promise的用法
我們封裝的ajax依然有很多的缺點
- 可以花時間把ajax寫到完美(花時間)
- 使用jQuery.ajax
- 使用axios(最新)
jQuery.ajax已經非常完美了,可以看jQuery中文文檔,但是不用掌握因為沒有人用了,現(xiàn)在用axios