異步與同步
異步: 所謂異步,簡單來說就是 不能連續執行,上一個任務沒運行完,下一個任務照樣運行,任務之間不能連續,不能首尾相連。
同步: 同步則與異步相反,是 連續執行 的,下一個任務緊接着上一個任務之後運行,如果上一個任務沒有運行完,下一個任務沒法運行。
異步的解決方案
1、回調函數
當我們需要在執行的函數中進行其他的操作,就需要用到回調函數了。如:
function fn(args, callback){
//TODO...
callback();
}
fn(args, ()=> {
//TODO...
})
2、事件監聽
如DOM事件的監聽、鼠標鍵盤的事件監聽等,都是異步執行的。
document.addEventListener("click", (e)=>{
//TODO...
}, false)
3、發佈/訂閱
比如我要開一家新公司,供用戶加入,當我在新公司發佈消息時,只有註冊用戶才能收到消息。
我們架構程序時,我們可以從使用出發,比如先寫我們要怎麼用,如:
// 首先要開新公司
const company = new Company();
// 然後需要有些用戶吧,比如用戶A、B等等
// 用戶A
const userA = function(msg){
console.log(`用戶A接收到的消息爲:${msg}`)
}
// 用戶B
const userB = function(msg){
console.log(`用戶B接收到的消息爲:${msg}`)
}
// 再接着把這些用戶註冊到新公司裏(他們就是這公司的註冊用戶了)
company.addSubscribe(userA, userB);
// 這樣我就可以以公司的名義向用戶發送消息了吧
company.release('今天公司成立,註冊用戶每人獎勵一萬元整。');
然後接下來我們是不是要考慮怎麼實現這個Company啦(就是註冊這公司有些什麼樣的手續及後續的功能)。如:
// 首先我們是不是要定義一個這樣的類啊
class Company {
constructor(){
// 在類裏面我們要定義一個存儲用戶數據的變量
this.users = [];
}
// 前面我們用到的添加用戶的功能
addSubscribe(...args){
args.forEach((val) => {
if(!this.users.includes(val)){
this.users.push(val);
}
})
return this;
}
// 還需要發佈消息的功能吧
release(msg){
this.users.forEach((fn) => {
fn(msg);
})
return this;
}
}
4、Promise(es6新增)
es6新增的異步處理,如果按第1項裏的回調函數,如果多層調用會很麻煩,如ajax:
// 比如我們要先獲取token,再根據token去獲取用戶信息,然後根據用戶信息獲取其他的內容等等
// 就得這樣(這樣一直嵌套感覺很不爽)
ajax(option, (res) => {
// 拿到token
ajax(option, (res) => {
// 拿到用戶信息
ajax(option, (res) => {
// 拿到其他等等數據
})
})
})
那麼在es6裏面我們可以怎麼做呢,Promise的語法什麼的我就不說了,我只講思路。
如:
// 封裝一個ajax方法
function myAjax(option){
return new Promise((resolve, reject) => {
ajax(option, (res) => {
resolve(res);
}, (err) => {
reject(err);
})
});
}
// 此時我用這個方法去完成上面的需求
myAjax(option).then(res => {
// 拿到token
return myAjax(res.token);
}).then(res => {
// 拿到用戶信息
return myAjax(res.info);
}).then(res => {
// 拿到其他等等數據
console.log(res);
})
generator函數(es6新增)
簡單來說,她是個可控制運行的函數,函數體內的內容是可控的。你叫她往下運行她就往下運行。來看一個簡單的例子:
function* idMaker(){
let index = 0;
while(true)
yield index++;
}
let gen = idMaker(); // 返回 generator 對象
// gen.next()方法,返回 yield 表達式產生的值
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...
有人就會問了,平時咋用啊,貌似也用不到吧。那我們就結合上面Ajax,來實現一個類似需求,我們經常會遇到在一個內容很多的頁面上,我們是不是需要先加載什麼後加載什麼呢,比如我們得先把菜單列表的數據請求過來,再去請求新聞啊、活動啊之類的,對吧。那麼我們來看看怎麼玩。
// 這裏我們直接用上面封裝的myAjax方法
// 首先我們要定義一個generator函數
function* gen(){
yield myAjax(option); // 請求菜單
yield myAjax(option); // 請求新聞
yield myAjax(option); // 請求活動
}
let g = gen();
g.next().value.then(res => {
console.log('菜單請求成功');
return g.next().value; // 菜單請求成功之後,接着往下請求
}).then(res => {
console.log('新聞請求成功');
return g.next().value; // 新聞請求成功之後,接着往下請求
}).then(res => {
console.log('活動請求成功');
})
// 看完是不是感覺跟前面的Promise一樣啊,對,沒錯,就是一樣的。唯一不同的是什麼呢,我們把請求的參數放上一塊了(option放到了gen函數裏面了),這樣就方便代碼管理啦。
但是呢,每次都要手機調用,也很麻煩,再說了,generator語言化也不怎麼友好,比如“*”號啥意思?“yield”關鍵字又是啥意思?感覺不符合場景。有更好的東東嗎?當然有,請接着往下看!
async、await(es6新增)
沒錯,就是她了,字面意思已經告訴你了 “async”異步的意思,“await”等待。那她怎麼用呢,看例子:
// 這裏我們還是直接用上面封裝的myAjax方法
// 定義一個異步函數
async function fn(){
let token = await myAjax(option); // 拿到token
let info = await myAjax(token); // 根據token去拿用戶信息.
let other = await myAjax(info); // 根據info去拿其他信息
}
// 最後我們再調用這個函數
fn() // async函數,返回的是一個Promise對象,你看哪兒哪兒都有Promise,是不是很重要
// 那麼既然是一個Promise對象,可不可以.then 或 .catch啊,當然可以
async function fn2(){
return 'allen'
}
fn2().then(res => {
console.log(res); // allen
})
// 這樣是不是方便很多,語義化也很好啊。
這裏有幾個點需要注意:
1、await只能放在async函數中。
2、await後面可以是任何對象
3、async函數返回的是一個Promise對象
4、如果await後面的Promise狀態變爲reject,那麼會停止整個async函數
總結
總的來說,以後常用的估計就是Promise與async函數、await的配合使用了,當然我們首先得了解Promise,瞭解異步。謝謝大家。
注:愛護原創,轉載請說明出處