異步與Promise(面試必考)
AJAX(Async JavaScript And XML)
內容:Ajax異步編程在js里的統(tǒng)一解決方案(js異步編程模型) Promise
什么是異步?什么是同步?
同步:能直接拿到結果
比如你在醫(yī)院掛號,拿到號才會離開窗口。
同步任務可能消耗10ms,也可能需要3s
總之不拿到結果你是不會離開的
異步:不能直接拿到結果
比如你在餐廳門口等位,拿到號可以去逛街
什么時候才能真正吃飯呢?
你可以每10min去餐廳問一下(輪詢)
你也可以掃碼用微信接收通知(回調)
異步通常指"異步加回調"
舉例
1.以ajax為例
request.send()之后,并不能直接得到response
不信console.log(request.response)試試
必須等到readState變?yōu)?后,瀏覽器回頭調用
request.onreadystatechange函數
我們才能得到request.response
這跟餐廳給你發(fā)送微信提醒的過程是類似的
補充:在js中發(fā)一個網絡請求并得到響應大概要幾百ms~1/2s
getJSON.onclick = () => {
...
request.send()
console.log(request.response) //不能直接得到response
setTimeout(() => { //2s后得到response
console.log(request.response)
}, 2000)
}
下載完成后瀏覽器會回調request.onreadystatechange函數
所以上面的代碼等價于
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status == 200) {
console.log(request.response)
...
}
打印出response后就是3s后
2.回調callback
你寫給自己用的函數,不是回調
你寫給別人用的函數,就是回調
request.onreadystatechange就是我寫給瀏覽器調用的
意思就是瀏覽器回頭調一下這個函數
「回頭」也有「將來」的意思,如「我回頭請你吃飯」
舉例
1.把函數1給另一個函數2
function f1(){}
function f2(fn){
fn()
}
f2(f1)
我調用f1沒有?沒有
我把f1傳給f2了沒有?傳了
f2調用f1了沒有?調了
那么f1是不是我寫給f2調用的函數?是,所以f1是回調
沒有調用、傳給別人了、別人調用了
補充
//request.setCallback(onreadystatechange)
request.onreadystatechange
是不是參數這個問題并不大,直接寫一樣的效果
不過一般不推薦放到對象上,最好作為參數傳給它,防止別人不知道
抬杠1,如果我傳給f2的參數不是函數呢?
會報錯:fn不是一個函數??吹綀箦e你不就知錯了
抬杠2
function f1(x){
console,log(x)
}
function f2(fn){
fn('你好')
}
f2(f1)
f1怎么會有一個x參數
fn('你好')中的fn就是f1對吧
fn('你好')中的'你好'會被賦值給參數x對吧
所以x就是'你好'。x表示第1個參數而已
異步和回調的關系
回調就是我把一個函數傳給你 或者傳到全局函數request上
關聯(lián)
異步任務需要在得到結果時通知js來拿結果
怎么通知呢?
可以讓js留一個函數地址(電話號碼)給瀏覽器
異步任務完成時瀏覽器調用該函數地址即可(撥打電話)
同時把結果作為參數傳給該函數(電話里說可以來吃了)
這個函數是我寫給瀏覽器調用的,所以是回調函數
區(qū)別
異步任務常常用到回調函數來通知結果,但不一定非要用回調。也可以用輪循
回調函數也不一定只用在異步任務里,也可以用到同步任務里
array.forEach(n=>console.log(n))就是同步回調
array里有多少個元素forEach就會調用函數n多少次
判斷同步異步
怎么區(qū)分一個函數是同步還是異步?
根據特征或文檔
如果一個函數的返回值處于這3個東西內部,那么這個函數就是異步函數
setTimeout
Ajax(即XMLHttpRequest)
AddEventListener
等下,我聽說Ajax可以設置為同步的
傻X前端才把Ajax設置為同步的,這樣會使請求期間頁面卡住。
例子:異步1個結果的處理
1s后返回1~6的隨機數
function 搖骰子(){
setTimeout(()=>{
return parseInt(Math.random() * 6 ) + 1
},1000)
//return undefined
}
const n=搖骰子()
console.log(n) //undefined
搖骰子()沒有寫return,那就是return undefined
箭頭函數里有return,返回真正的結果
注意這2個return屬于不同的函數
所以這是一個異步函數/任務
那怎么才能拿到異步結果(1~6的隨機數)?
可以用回調。寫個函數,然后把函數地址給它
function f1(x){console.log(x)}
搖骰子(f1)
然后我要求搖骰子函數得到結果后把結果作為參數傳給f1
function 搖骰子(fn){
setTimeout(()=>{
fn(parseInt(Math.random() * 6 ) + 1) //得到結果后傳給fn
},1000)
}
將結果傳給fn,這時候就能輸出結果了。
簡化為箭頭函數。f1聲明后只用了一次,所以可以刪掉f1
優(yōu)化技巧: 函數聲明后只用了一次時,可以簡化為匿名函數
function f1(x){console.log(x)}
搖骰子(f1)
改為
搖骰子(x=>{
console.log(x)
})
再簡化為
搖骰子(console.log)
如果參數個數不一致就不能這樣簡化,有個面試題
搖骰子(x,y=>{
console.log(x)
})
面試題
const array=['1','2','3'].map(parseInt)
console.log(array)
輸出結果:[1, NaN, NaN]
還原
const array=['1','2','3'].map((item,i,arr)=>{
return parseInt(item,i,arr)
//parseInt('1',0,arr)=>1
//parseInt('2',1,arr)=>NAN 一進制沒有2所以得到NAN
//parseInt('3',2,arr)=>NAN 二進制沒有3所以得到NAN
})
console.log(array)
輸出結果:[1, NaN, NaN]
map接收3個參數
把2作為1進制數進行解析,2進制數只有0和1,1進制數只有0,10進制只有0~9。因為一進制沒有2所以得到的結果不是一個數字NAN。
正確簡化
const array=['1','2','3'].map((item,i,arr)=>{
return parseInt(item)
})
console.log(array)
輸出結果:[1, 2, 3]
簡寫
const array=['1','2','3'].map((item=>parseInt(item))
console.log(array)
總結
異步任務不能拿到結果,于是我們傳一個回調給異步任務
異步任務完成時調用回調,調用的時候把結果作為參數
異步為什么會用到回調?為了拿到不能直接拿到的結果,必須用回調/輪詢
Promise的用法
Promise是前端解決異步問題的統(tǒng)一方案,異步2個結果(成功、失敗)的處理
如果異步任務有2個結果成功和失敗,怎么辦?
方法1:回調接受兩個參數(node.js就是用的這個方案,接收兩個參數)
fs.readFile('./1.txt',(error,data)=>{//2個參數:失敗的錯誤,成功的結果
if(error){console.log('失敗'); return}
console.log(data.toString()) //成功
)
方法2:搞兩個回調
ajax('Get','./1.json',data=>{},error=>{})
//前面函數是成功回調,后面函數是失敗回調
或者
ajax('Get','./1.json',{
success:()=>{},fail:()=>{}
})
//接受一個對象,對象有兩個 key 表示成功和失敗
不管方法1還是方法2都有問題,有3個缺點(回調的3個問題)
1.不規(guī)范沒有成文的規(guī)定。名稱五花八門,有人用success+error,有人用success+fail或者done+fail
2.容易出現(xiàn)回調地獄,代碼變的看不懂
3.很難進行錯誤處理
回調地獄舉例
getUser(user=>{
getGroups(user,(groups)=>{
groups.forEach((g)=>{
g.filter(x=>x.ownerId===user.id).forEach(x=>console.log(x))
})
})
})
這還只是四層回調,你能想象20層回調嗎?
有什么辦法能解決這3個問題?用promise
規(guī)范回調的名字或順序
拒絕回調地獄,讓代碼可讀性更強
很方便地捕獲錯誤
promise思想是在1976年提出的,后來被前端抄襲的。
promise是什么?1976年的一種設計模式(寫得好的代碼取了個名)
以ajax的封裝為例,解釋promise的用法
示例:寫一個回調的封裝
//1.ajax的定義
ajax=(method,url,options)=>{
const {success,fail}=options //析構賦值,從options里拿到success和fail這2個回調函數
const request=new XMLHttpRequest() //全局變量request
request.open(method,url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
//成功就調用success,失敗就調用fail
if(request.status < 400){
//一般來說不會出現(xiàn)300的,300不應該出現(xiàn)在這里,300會再發(fā)一個請求
success.call(null,request.response)
}else if(request.status >= 400){
fail.call(null,request,request.status)
}
}
}
request.send()
}
//2.ajax的使用
ajax('Get','/xxx',{
success(response){},fail:(request,status)=>{}
//ES6語法:左邊是function縮寫,右邊是箭頭函數
})
ES 6語法:析構賦值
const {success,fail}=options //析構賦值,從options里拿到success和fail這2個回調函數
//等價于
//const success=options.success
//const fail=options.fail
Promise說這代碼太傻了,我們改成promise寫法
ajax('Get','/xxx',{
success(response){},fail:(request,status)=>{}
})
//上面用到了兩個回調,還使用了success和fail.
//改成promise寫法
ajax('Get','/xxx').then((response)=>{},(request)=>{})
雖然也是回調,但是不需要記success和fail了
then的第1個參數就是success,第2個參數就是fail
promise規(guī)定了成功失敗都只能返回一個參數
ajax()返回了個啥?
返回了一個含有.then()方法的對象
那如何得到這個含有.then()的對象呢?
那就要改造ajax的源碼了
ajax=(method,url,options)=>{
return new Promise((resolve,reject)=>{ //1處
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) //2處
}else if(request.status >= 400){
reject.call(null,request,request) //3處
}
}
}
request.send()
})
}
符合promise規(guī)范的ajax調用,讓回調的異步函數變成promise的異步函數
步驟
第1步(定義時), 加上return new Promise((resolve,reject)=>{...})
任務成功調resolve(result),失敗調reject(error)
resolve和reject會再去調用成功和失敗函數
第2步(使用時), 使用.then(success,fail)傳入成功和失敗函數
如何使用Promise?
背下來5個詞return new promise((resolve,reject)=>{...}
總結
背下來:return new promise((resolve,reject)=>{成功時調用resolve,失敗時調用reject}
//return 構造函數(參數)
關于異步
1.如果 JS 不能直接拿到一個函數的結果,可以先去執(zhí)行別的代碼,等結果到了再取結果,這就是異步
2.異步的結果可以通過輪詢獲取,輪詢就是定時去詢問結果拿到了沒有
3.異步的結果可以通過回調獲取,一般來說結果會被作為回調的第一個參數
4.異步的好處是可以把用來等待的時間拿去做別的事情
關于回調
1.滿足某些條件的函數才被稱為回調,比如我寫一個函數 A,傳給另一個函數 B 調用,那么函數 A 就是回調
2.回調可以用于同步任務,不一定非要用于異步任務
3.有的時候回調還可以傳給一個對象,如 request.onreadystatechange,等待瀏覽器來調用
關于Promise
1.Promise不是前端發(fā)明的,是目前前端解決異步問題的統(tǒng)一方案。
2.window.Promise 是一個全局函數,可以用來構造 Promise 對象
3.使用return new Promise((resolve, reject)=> {})就可以構造一個Promise對象
構造出來的Promise對象含有一個 .then()函數屬性
補充:注意Promise不可取消。 為了解決取消promise這個問題,axios自己想了個辦法就是用CancleToken。
原理就是將請求編號,假設發(fā)了10個promise,其中9個不要了。就把id為1~9的promise對應的ajax請求終止。promise還是做,只不過ajax不要了。axios取消的不是promise而是請求。axios.CancleToken
關于return new Promise((resolve, reject)=>{...})中的resolve和reject
1.resolve 和 reject 可以改成任何其他名字,不影響使用,但一般就叫這兩個名字。
2.任務成功的時候調用resolve,失敗的時候調用reject
3.resolve和reject都只接收一個數據,而且this是空,不應該用this
4.resolve和reject并不是 .then(success, fail)里面的success和fail,resolve會去調用 success,reject會去調用fail
我們封裝的ajax的缺點
1.post無法上傳數據 request.send(這里可以上傳數據)
2.不能設置請求頭 request.setRequestHeader(key,value)
怎么解決呢?
1.使用axios (推薦)
2.使用jQuery.ajax
我們需要掌握jQuery.ajax嗎?不用,寫篇博客羅列下功能,就可以忘掉jQuery了
3.自己花時間把ajax寫到完美
axios庫
Axios 是一個基于 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。
目前最新的Ajax庫,Vue、React都在用它。
這個庫比jQuery逼格高,現(xiàn)在專業(yè)前端都在用axios,它抄襲了jQuery的封裝思路。
推薦通過寫博客來學習一個庫axios速查表
代碼示例
axios.get('/5.json')
.then(response=>
console.log(response)
)
axios
這是一個專門用于操作 AJAX 的庫
axios.get('/xxx') 返回一個 Promise 對象
axios.get('/xxx').then(s, f) 在請求成功的時候調用 s,失敗的使用調用 f
axios高級用法
1.JSON自動處理
axios如何發(fā)現(xiàn)響應的Content-Type是json
就會自動調用JSON.parse
所以說正確設置Content-Type是好習慣
2.請求攔截器
你可以在所有請求里加些東西,比如加查詢參數
只要在這個函數里對config做一些修改,那你發(fā)的請求就會全部被你自己篡改。
比如說,我們要在請求里統(tǒng)一加個參數,不管這個請求是怎樣的都要加一個參數。
那么就可以加個攔截器。
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
3.響應攔截器
你可以在所有響應里加些東西,甚至改內容
function(response){ return response;}得到原始響應數據。如果你對數據不滿意,可以對它進行修改。這樣你就可以對它進行一些測試。
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
4.可以生成不同實例(對象)
不同實例可以設置不同的配置,用于復雜場景
instance就是axios的復制品
var instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
封裝!封裝!封裝!
初級程序員學習API(包括Vue/React的API)
中級程序員學習如何封裝
高級程序員造輪子
如何使用Axios? 設置下引用即可使用
打開BootCDN搜索axios-> 選擇axios.min.js,點擊復制<script>標簽
查看示例
console.log(axios)控制臺打印出了函數,說明axios存在,這樣就引用成功了(引用腳本)。
axios.get('/xxx')請求發(fā)送成功了(由于沒寫內容所以是404)
面試題
為什么我們要用promise啊,能不能講講原因?
思路:promise能解決回調的3個問題
1.規(guī)范回調的名字或順序
2.拒絕回調地獄,讓代碼可讀性更強
3.很方便地捕獲錯誤