JS異步之callback、promise、async+await簡介

所謂同步執行,就是從前日後執行,一句執行完了執行下一句。javascript

而異步執行,則不會等待異步代碼執行完,直接執行後面的代碼;等異步代碼執行完返回結果以後再進行處理。java

好比一些耗時操做會使用異步執行的方式,以提升執行效率,最多見的莫過於Ajax網絡請求。ajax

以jQuery Ajax爲例,假設有這樣的需求:編程

從地址A獲取數據a,從地址B獲取數據b,而後把數據a和b提交到接口Capi

ES5:使用callback實現

使用最原始的方式直接來寫,大概要寫成這樣子:promise

// 顯示加載提示
$.showLoading();

// 發起ajax請求
$.ajax({
    url: 'https://example.com/api/a',
    type: 'get',
    success: function(res1){
        $.ajax({
            url: 'https://example.com/api/b',
            type: 'get',
            success: function(res2){
                $.ajax({
                    url: 'https://example.com/api/c',
                    type: 'post',
                    success: function(res3){
                        $.hideLoading();
                        $.toast('請求成功');
                    },
                    error: function(res3){
                        $.alert('請求失敗: ' + res3);
                    }
                });
            },
            error: function(res2){
                $.alert('請求失敗: ' + res2);
                $.hideLoading();
            }
        });
    },
    error: function(res1){
        $.alert('請求失敗: ' + res1);
        $.hideLoading();
    }
});
複製代碼

這就產生了回調嵌套的問題,寫出來的代碼既難看又不方便維護。網絡

要解決這個問題,能夠寫個函數包裝一下,把類似的邏輯分離出來執行,好比:異步

// 請求過程包裝函數
function Process(){
    this.requests = []; // 保存請求方法
    this.count = 0; // 記錄執行到第幾個
    this.result = []; // 保存返回結果
    this.onComplete = null; // 請求完成回調
    this.onSuccess = null; // 請求成功回調
    this.onError = null; // 請求出錯回調
    // 執行請求的方法
    this.continue = function(){
        if(this.count < this.requests.length){
            var fn = this.requests[i];
            fn();
            this.count += 1;
        } else {
            this.onComplete(this.result);
        }
    }
}

// 建立對象
var p = new Process();

// 將請求方法放入
p.requests.push(function(){
    $.ajax({
        url: 'https://example.com/api/a',
        type: 'get',
        success: function(res){
            p.onSuccess(res);
        },
        error: function(res){
            p.onError(res);
        }
    });
});
p.requests.push(function(){
    $.ajax({
        url: 'https://example.com/api/b',
        type: 'get',
        success: function(res){
            p.onSuccess(res);
        },
        error: function(res){
            p.onError(res);
        }
    });
});
// 當請求成功
p.onSuccess = function(res){
    // 存儲返回結果
    p.result.push(res);
    // 執行下一個
    p.continue();
};
// 當請求失敗
p.onError = function(res){
    $.alert('請求失敗: ' + res);
    $.hideLoading();
};
// 當請求完成
p.onComplete = function(result){
    $.ajax({
        url: 'https://example.com/api/c',
        type: 'post',
        data: {
            a: result[0],
            b: result[1]
        },
        success: function(res){
            $.hideLoading();
            $.toast('請求成功');
        },
        error: function(res){
            p.onError(res);
        }
    });
};

// 顯示加載提示
$.showLoading();
// 執行請求
p.continue();
複製代碼

這樣經過一個包裝函數統一進行處理,從而避免了回調嵌套的問題。async

根據不一樣的業務邏輯,不一樣的編程習慣,這類函數實現的方式也各不相同。那麼有沒有一種方式,能夠適用全部異步調用呢?ide

ES6:Promise

Promise就給出了這樣一套規範,可使用Promise來處理異步操做。

Promise規範詳細的內容和實現比較複雜,須要另寫一篇文章,這裏就只介紹一下Promise的用法。

如何用Promise執行jQuery Ajax請求:

// 首先建立一個Promise對象實例,傳入一個執行函數
// 執行成功時調用resolve方法,傳入返回結果
// 執行失敗時調用reject方法,傳入錯誤緣由
let p = Promise(function(resolve, reject){
    $.ajax({
        url: 'https://example.com/api/a',
        type: 'get',
        success: function(res){
            resolve(res);
        },
        error: function(res){
            reject(res);
        }
    });
});
// promise有一個then方法,在then方法中執行回調
// 第一個是成功回調,第二個是失敗回調
p.then(function(data){
    console.log('請求成功', data);
}, function(err){
    console.error('請求失敗', err);
});
複製代碼

也能夠只傳入成功回調,在後面用catch方法來捕獲錯誤,這樣能夠統一處理錯誤:

// 使用catch方法捕獲錯誤
p.then(function(data){
    console.log('請求成功', data);
}).catch(function(err){
    console.error('請求失敗', err);
});
複製代碼

Promise的鏈式調用:

// 在回調中返回新的Promise,能夠在下一個then中執行回調,實現鏈式調用
p.then(function(data){
    console.log('A請求成功', data);
    return new Promise(function(resolve, reject){
        $.ajax({
            url: 'https://example.com/api/b',
            type: 'get',
            success: function(res){
                resolve(res);
            },
            error: function(res){
                reject(res);
            }
        });
    });
}).then(function(data){
    console.log('B請求成功', data);
}).catch(function(err){
    console.error('請求失敗', err);
});
複製代碼

前面需求的完整Promise方式實現:

// 用於返回jQuery Ajax的Promise方法,從而簡化代碼
let promisifyAjaxRequest = function(url, data){
    let method = data ? 'post' : 'get';
    return new Promise(function(resolve, reject){
        $.ajax({
            url: url,
            type: method,
            data: data,
            success: function(res){
                resolve(res);
            },
            error: function(res){
                reject(res);
            }
        });
    });
};

// 顯示加載提示
$.showLoading();
// 用於保存請求數據結果
let result = [];
promisifyAjaxRequest('https://example.com/api/a').then(function(data){
    result.push(data);
    return promisifyAjaxRequest('https://example.com/api/b');
}).then(function(data){
    result.push(data);
    return promisifyAjaxRequest('https://example.com/api/c', {
        a: result[0],
        b: result[1]
    });
}).then(function(data){
    $.hideLoading();
    $.toast('請求成功');
}).catch(function(err){
    $.alert('請求失敗: ' + err);
    $.hideLoading();
});
複製代碼

能夠看到使用Promise以後的代碼變得更加簡潔和統一。

可是Promise也有不足之處,就是使用起來不是特別直觀。

ES7:async+await

在ES7中,提供了async和await關鍵字,被稱爲異步的終極解決方案。

這個方案其實也是基於Promise實現的,具體細節就先不展開,仍是重點來看使用方式。

使用async+await來實現前面需求:

// 由於基於Promise實現,因此一樣須要返回Promise
let promisifyAjaxRequest = function(url, data){
    let method = data ? 'post' : 'get';
    return new Promise(function(resolve, reject){
        $.ajax({
            url: url,
            type: method,
            data: data,
            success: function(res){
                resolve(res);
            },
            error: function(res){
                reject(res);
            }
        });
    });
};

// 函數定義前面加上async關鍵字
async function ajaxRequests(){
    // promise方法前面加上await關鍵字,能夠接收返回結果
    let a = await promisifyAjaxRequest('https://example.com/api/a');
    let b = await promisifyAjaxRequest('https://example.com/api/b');
    let data = await promisifyAjaxRequest('https://example.com/api/c', {
        a: a,
        b: b
    });
    return data;
}

// 顯示加載提示
$.showLoading();
// 執行請求
// 用法同Promsie
ajaxRequets().then(function(data){
    $.hideLoading();
    $.toast('請求成功');
}).catch(function(err){
    $.alert('請求失敗: ' + err);
    $.hideLoading();
});
複製代碼

能夠看到,async+await的寫法更加直觀,也更像同步的寫法。

相關文章
相關標籤/搜索