博客 / 詳情

返回

前端-JavaScript異步編程async函數

基本概念

傳統JavaScript異步編程的形式大體分以下幾種。

  • 回調函數
  • 事件監聽
  • 發佈/訂閲
  • Promise 對象

異步

一個任務連續的執行就叫做同步。如果將任務為分兩步執行,執行完第一步,轉而執行其它任務,等做好了準備,再回過頭執行第二步,這種不連續的執行就叫做異步。

回調函數

回調函數就是把第二步執行的任務單獨寫在一個函數裏面,等到重新執行這個任務的時候,就直接調用這個函數。回調函數的英語叫callback,直譯過來就是"重新調用"。

loadData(url, function (data) {
  console.log(data);
});

注意:任務第一步執行完後,所在的上下文環境就已經結束了,所以我們一般會使用var that = this 將第一步執行時的this 指向進行保存,以便回調時使用。

function Api(url) {
    this.url = url;
    this.request = function () {
          var that = this
        setTimeout(function () {
            console.log('url', that.url)
        }, 1000)
    }
}

var api = new Api('http://127.0.0.1')
api.request() // url http://127.0.0.1

Generator函數

異步編程解決方案中, ES6還提供了Generator函數。它其實是一個普通函數,獨有特徵

  1. function關鍵字與函數名之間有一個星號;
  2. 函數體內部使用yield表達式,定義不同的內部狀態。

function* statusGenerator() {
  yield 'pending';
  yield 'running';
  return 'end';
}

var st = statusGenerator();

上面代碼 statusGenerator 函數返回一個迭代器對象,函數內定義了三個狀態,調用迭代器next方法指向下一個狀態。

st.next() // { value: 'pending', done: false }
st.next() // { value: 'running', done: false }
st.next() // { value: 'end', done: false }

yield 表達式

yield表達式就是暫停標誌。迭代器執行next時。

  1. 遇到yield表達式,就暫停執行後面的操作,並將yield後面的那個表達式的值作為返回的對象的value屬性值。
  2. 下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。
  3. 如果沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句為止,並將return語句後面的表達式的值,作為返回的對象的value屬性值。
  4. 如果該函數沒有return語句,則返回的對象的value屬性值為undefined

for...of 循環

我們也可以使用 for...of進行遍歷。

function* statusGenerator() {
  yield 'pending';
  yield 'running';
  return 'end';
}

var st = statusGenerator();
for(let v of st){
  console.log(v)// pending running
}

Generator 的應用

協程

協程的意思是多個線程互相協作,完成異步任務。它是一些編程語言的異步編程方案,比如go中的協程實現goroutine。協程序執行的大致流程如下:

  1. 協程A開始執行。
  2. 協程A執行到一半,進入暫停,執行權轉移到協程B
  3. (一段時間後)協程B交還執行權。
  4. 協程A恢復執行。

JavaScript中的協程實現Generator 函數,它可以在指定的地方(yield)交出函數的執行權(即暫停執行),然後等待執行權交還繼續執行。

比如:我們實現一個倒計時函數,任務就緒後等待倒計時,一起執行。

function* countdown(num, running) {
    do {
        yield num--
    } while (num > 0)
    running()
}

const tasks = []
const ct = countdown(3, function () {
    console.log('start run task')
    for (let task of tasks) {
        task()
    }
})

for (let i = 0; i < 3; i++) {
    tasks.push(function () {
        console.log('task '+ i)
    })
    ct.next()
}

ct.next()

一個異步請求封裝

var fetch = require('node-fetch');

function* request(){
  var url = 'xxxx';
  var user = yield fetch(url); // 返回promise對象,data: {'user':'xxxx'}
  console.log(user);
}

var req = request();
var result = req.next();

result.value.then(function(data){
  return data.user
}).then(function(user){
  req.next(user);                        // 將 user信息傳到 request()函數,被user變量接收。
});

async函數

ES2017 引入了 asyncawait 關鍵字,使用這對關鍵字,可以用更簡潔的方式寫出基於Promise的異步行為,而無需刻意地鏈式調用promise

async聲明的函數一般稱為async函數。可以把 async 看作是 Generator 的語法糖,因為它們本質的作用一樣。

Generator 寫法

const loadData = function (url) {
    return new Promise(function (resolve, reject) {
        resolve(data);
    });
};

const request = function* () {
    const user = yield loadData('https://user');
    const goods = yield loadData('https://goods');
    console.log(user, goods);
};

async 寫法

const loadData = function (url) {
    return new Promise(function (resolve, reject) {
        resolve(data);
    });
};

const request = async function () {
    const user = await loadData('https://user');
    const goods = await loadData('https://goods');
    console.log(user, goods);
};

基本用法

async函數會返回一個 Promise 對象。當函數執行的時候,一旦遇到await就會先返回,等到異步操作完成,再接着執行函數體內後面的語句。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello', 50);

async函數內部return語句返回的值,會成為then方法回調函數的參數。

async function hello() {
  return 'hello';
}

hello().then(v => console.log(v))
// "hello"

async函數內部拋出錯誤,會導致返回的 Promise 對象變為reject狀態。拋出的錯誤對象會被catch方法回調函數接收到。

async function hello() {
  throw new Error('Error');
}

hello().then(
  v => console.log(v),
  e => console.log( e)
) // //Error: Error

await 命令

一般情況下,await後面都是一個 Promise 對象,返回該對象的結果。如果不是 Promise 對象,就直接返回對應的值。

async function hello() {
  return await 'hello'
}
hello().then(v => console.log(v)) // hello

async function hello() {
  return await Promise.resolve('hello');
}
hello().then(v => console.log(v)) // hello

錯誤處理

如果await後面的異步操作出錯,那麼等同於async函數返回的 Promise 對象被reject

async function hello() {
  await new Promise(function (resolve, reject) {
    throw new Error('error');
  });
}

hello()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:error

所以最好把 await命令放在try...catch代碼塊中。

async function hello() {
    try {
        await new Promise(function (resolve, reject) {
            throw new Error('error');
        });
    } catch(e) {
        console.log('err:', e) // error
    }
    return await('hello');
}

const  h = hello();
h.then((v) => {console.log(v)}) // hello

小結

本文記錄了JavaScript異步編程中的一些方式,Generator函數和 asyncawait 語法,歡迎留言交流。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.