js的異步請求歷來被詬病,但是社區和規範一直也在努力,這裏簡單說下這些變化。
ajax
嚴格地說ajax屬於與服務器交換數據的API,與異步並不完全相同。但對於早期的前端來說,異步的操作基本都是與ajax交涉的過程。
2005年ajax-new-approach-web-application一文催生了ajax技術,隨後各瀏覽器紛紛實現了XmlHttpRequest。其具有劃時代的意義,爲網頁性能和用戶體驗帶來了巨大的提升。下面是一個基於XmlHttpRequest對象實現的簡單取數據的函數:
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function(){if(xhr.readyState === 4){
if(xhr.status == 200){
console.log(xhr.responseText)
}
}}
xhr.open('GET','https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState',true)
xhr.send(null)
可以看出這個對象具有濃濃的面向對象的風格,沒有函數式編輯的優雅。目前作爲XHR的替代api——fetch,則改正了這一股風氣。
jquery deffered
這玩意已經退出歷史舞臺了,我差不多已經忘記了。但是17年去頭條面試還居然問我$deffered是怎麼實現的,要寫代碼。我表示很崩潰。
promise
promise 是es6的規範,它的規則如下:
promise的參數是一個函數,這個函數執行時會被注入兩個參數,resolve和reject,這兩個函數會改變promise對象的狀態,狀態發生變化時則會執行相應的回調
具體有三個狀態:
- pending
- fullfilled
- rejected
狀態轉換規則如下:
所以對於一個連續三次的異步操作,它的代碼可能如下:
function resolveNumAfter2S(x){
return new Promise(resolve=>{
setTimeout(()=>{resolve(x)}, 500)
})
}
resolveNumAfter2S(10)
.then(x => {return resolveNumAfter2S(x+1)})
.then(x => {return resolveNumAfter2S(x+2)})
.then(x => {console.log(x)})
promise 隱藏了中間一些抽象的處理,resolve,reject導致的狀態變化以及它們的實現,then,catch的綁定都隱藏了,所以理解起來會有些困難,如果自己用js寫一個類似的實現,則能容易理解它的功能,下面是一個小意思:
const MyPromise = function(f){
f.bind(this, this._resolve.bind(this), this._reject.bind(this))()
}
MyPromise.prototype = Object.assign(MyPromise.prototype, {
status: 'pending',
_resolve: function (data) {
this.status = 'fullfilled';
this._then(data);
},
_reject: function (err) {
this.status = 'rejected';
this._catch(err);
},
then: function (f) {
this._then = f;
return this;
},
catch: function (f) {
this._catch = f;
return this;
}
})
function resolveAfter2s(x){
return new MyPromise((reslove,reject)=>{
setTimeout(()=>{
reject("err")
}, 2000)
})
}
var res = resolveAfter2s(10).then(x=>{
console.log(x)
}).catch(err => {
console.log(err)
})
async await
作爲es7的終級異步解決方案,async/await的規則如下:
- 使用async前綴定義的函數,會返回一個promise對象
- await只能在async function 中使用
- 遇到await 指令,異步代碼的執行和同步代碼一樣,不會跳過
對於一個簡單的例子,大約很難體會它到底有什麼用, 比如:
function resolveNumAfter2S(x){
return new Promise(resolve=>{
setTimeout(()=>{resolve(x)}, 2000)
})
}
async function getNum(){
return await resolveNumAfter2S(1)
}
getNum().then(x=>{
console.log(x)
})
但考慮以下場景,就會發現其方便之處:
function resolveNumAfter2S(x){
return new Promise(resolve=>{
setTimeout(()=>{resolve(x)}, 500)
})
}
// 假設你有一個需求,需要請求三個異步的數據,而每一步都要依賴上一步的數據,你完全可以以同步的代碼體驗去寫代碼:
async function getNum(){
let a = await resolveNumAfter2S(6);
let b = await resolveNumAfter2S(a);
let c = await resolveNumAfter2S(b+a);
return a+b+c;
}
getNum().then(x=>{
console.log(x)
})