javascript - 非同步操作

寫不好程式碼的格子襯衫發表於2019-01-27

javascript與其他語言的經典不同在於,javascript是非同步的,而其他語言是同步的。這裡,我們介紹一下javascript中非同步的幾種方式。

幾種非同步方式

  • 回撥函式
  • promise
  • generator
  • async / await

回撥函式

回撥函式,是早期js中廣泛使用的一種回撥方式,jquery中的ajax方法就是經典的回撥函式模式。

回撥函式的寫法中,回撥是放在函式引數裡面的,執行的過程看起來沒有同步寫法那麼明瞭。

在回撥次數多的情況下,可能會造成回撥地獄,就是巢狀太深。所以es後面提出的非同步方案也是不斷對非同步做優化。

$.ajax({
    url: 'http://binnie.com/getdata',
    type: 'GET',
    data: {},
    success: {
        // success todo
    },
    error: {
        // error todo
    }
})
複製程式碼

promise

promise,是es6中提供的一種解決方案,相比於回撥函式,promise的寫法更加同步化一些~

狀態

promise的狀態分為三種:

  • pending
  • resolved
  • rejected

狀態的變化只有兩個過程,並且狀態一旦發生改變,就永遠不可逆。

  • pending -> resolved
  • pending -> rejected

基礎

Promise是一個建構函式,我們可以使用new來進行例項化的操作。

我們來看一個簡單的?

我們可以看到new一個Promise物件的時候,傳入的是一個函式,該函式攜帶兩個引數:resolve&reject,分別為成功操作&失敗操作,函式內部的操作是同步執行的,所以當執行程式碼的時候,馬上就可以列印出1

new返回的物件是一個Promise物件,Promise物件有幾個內建的函式,這幾個函式是在Promise的同步函式執行完之後可能觸發的函式。

每次執行完其中一個函式,是可以接著鏈式執行的,因為每次執行完一次,都會返回一個新的Promise物件。

  • then
  • finally
  • catch
let promise = new Promise((resolve, reject) => {
    // 這裡是同步執行的
    console.log(1)
    // 非同步操作
    return resolve('binnie')
});
promise.then((resolve) => {
    // 成功回撥,即執行resolve()
    console.log('then')
},(reject) => {
    // 失敗回撥,即執行reject()
    console.log('err')
}).finally(() => {
    // 完成回撥,無論成功/失敗都會到的回撥
    console.log('finally')
}).catch((err) => {
    // 錯誤回撥,前面任何一步丟擲的錯誤
    console.log(err)
});

// 1
// then
複製程式碼

例項方法

例項方法是new Promise返回的物件的原型方法。比如then方法 -> Promise.prototype.then。

then

then方法接受兩個引數,第一個為resolve回撥,第二個為reject回撥

promise.then((resolve) => {
    // resolve引數為promise resolve的時候的引數
    // 當promise執行resolve時,會進入該回撥
},(reject) => {
    // reject引數為promise reject的時候的引數
    // 當promise丟擲異常 或者 執行reject時,會進入該回撥
})
複製程式碼

finally

finally回撥函式不接受任何引數,只做finally處理,類似請求的complete操作。

promise.finally(() => {
    // resolve or reject 之後的操作
})
複製程式碼

catch

catch方法,用於捕獲異常。

在promise中的異常可以在兩個位置進行捕獲,then方法的第二個函式 or catch方法。一般情況下,我們會在catch中捕獲。

在promise中,錯誤也是會冒泡的,如果then操作沒有新增reject方法,那麼錯誤就會在後面的catch捕獲,不過,如果沒有對獲取進行捕獲,錯誤其實會被promise自己吃掉,而不會暴露在最外面。

promise.then(() => {
    console.log('then')
    throw new Error()
}).catch((err) => {
    console.log(err)
})
複製程式碼

Promise方法

Promise方法,直接呼叫即可,返回的也是promise物件。

Promise.all(array)

Promise.all,接受一個陣列做為引數,引數中的物件會被轉化為promise物件

當陣列中的所有promise物件resolve時,all的promise的狀態才變為resolve

當陣列中的任一promise物件reject時,all的promise的狀態就變為reject

Promise.all 類似我們平常使用的 &&

Promise.all([promise,2,3]).then((resolve) => {
    // 引數也是一個陣列,為all陣列對應的resolve引數
    // ['binnie',2,3]
    console.log(resolve)
}).catch((err) => {
    // 錯誤為第一個reject時丟擲的錯誤
    console.log(err)
})
複製程式碼

Promise.race(array)

Promise.race跟all的用法類似,也是接受一個陣列做為引數

陣列中最先改變狀態的,那個狀態就會同步給race的狀態

Promise.race([1,promise,3]).then((resolve) => {
    // 引數為第一個狀態變為resolve的引數
    console.log(resolve)
}).catch((err) => {
    // 引數為第一個狀態變為reject的引數
    console.log(err)
})
複製程式碼

Promise.resolve()

返回一個狀態為resolved的promise物件。

Promise.resolve()可攜帶引數,引數會做為返回物件的引數。

Promise.reject()

返回一個狀態為rejected的promise物件。

Promise.reject()可攜帶引數,引數會做為返回物件的引數。

關於promise,也可檢視樓主之前的文章 ES6之promise基礎篇

generator

generator可以理解為一個暫停執行函式。每次執行next()來往下走,直到結束,狀態停止。

generator函式的寫法,就是在函式後面加一個星號 function* ,在函式內部,可以使用 yield 來進行暫停的操作。

function* foo() {
    console.log(0)
    yield 1
    yield 2
    yield 3
    return 4
    yield 5
}
// 呼叫generator函式的時候,函式並不會被執行
let f = foo()
console.log(f)         
// 呼叫next時函式才會開始執行,當遇到yield時暫停
// 下一次next時會從上次暫停的位置繼續執行
// 當遇到return時 or 函式執行完成時,函式結束
console.log(f.next())   
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
複製程式碼

javascript - 非同步操作

async / await

generator相對來說可讀性會比較差,於是乎有來async函式,表示函式內部會有非同步操作,async函式相當於是generator函式的語法糖。

下面我們來看下async函式的寫法

// 定義一個async函式,表明函式內部要執行非同步操作
async function fun() {
    // await後面跟著非同步操作
    // 函式會等待await操作完成之後再繼續往下走
    let obj = await request();
    return obj;
}
// async函式執行完之後會返回一個promise物件,函式返回值可再then中取到
let ret = fun()
ret.then((resolve) => {
    console.log(resolve)
})
複製程式碼

當async函式中遇到await時,就會進行等待,然後再執行下一步的操作,如果沒必要序列,我們可以改成並行的寫法,這樣可以節省函式執行時間。

// 序列寫法
async function fun() {
    let obj1 = await request1();
    let obj2 = await request2();
    return;
}

// 並行寫法
async function fun() {
    let obj1 = request1();
    let obj2 = request2();
    let ret1 = await obj1;
    let ret2 = await obj2;
    return;
}

// Promise.all寫法
async function fun() {
    let [obj1, obj2] = await Promise.all([request1(), request2()]) ;
    return;
}
複製程式碼

寫在最後

非同步操作的核心都是一樣的,只是寫法不同而已。

具體要採用哪種非同步方法,還是要看我們實際的專案需要。

相關文章