9102,Promise 和 Async 你會真的會用了嗎?

沒什麼,只是但願你取的人是我!     --- 時間旅行者的妻子ajax

前言

遙想當年剛接觸 JavaScript 時被回調地獄支配的恐懼,😂😂😂!json

解救之道,就在其中啊 --- Promise 和 Async/awaitapi

推薦 vscode 插件 Code Runner,能夠運行代碼片斷。學習利器啊。不過是基於 Node 運行環境數組

Promise

一個 Promise有如下幾種狀態: 狀態一旦改變,便不能更改。promise

pending: 初始狀態,既不是成功,也不是失敗狀態。等待狀態轉變異步

fulfilled: 意味着操做成功完成。then 處理邏輯async

rejected: 意味着操做失敗。能夠在 catch 處理邏輯ide

每個 then 都會返回一個新的 Promise 實例函數

Api

// 建立一個 fulfilled 狀態的 Promise 實例。
Promise.resolve();
// 建立一個 rejected 狀態的 Promise 實例。
Promise.reject();
// then 的第一個參數 resolve {Function} 處理 fulfilled 狀態,
// 第二參數rejected 處理 rejected 狀態。
// 若是 then 處理了 rejected 狀態,那麼返回的 Promise 實例爲 
// fulfilled,不然會返回的實例爲 rejected。
Promise.prototype.then(resolve,rejected);
// 攔截 rejected 狀態執行,並返回一個 fulfilled 狀態的 Promise 實例
Promise.prototype.catch();
// 無論什麼狀態最後都會執行
Promise.prototype.finally();
// 執行多個 promise 都執行玩了並將結果返回 Array [promiseRet,promiseRet]
Promise.all([promise,promise]);
// 返回昀行最快的那個 promise 的結果 
Promise.race([promise,promise]);
複製代碼

開胃小菜

  • demo1
const demo1 = function _demo1(num) {
    return new Promise((resolve, reject) => {
        if (num % 2 === 0) {
            // pending 狀態 到 fulfilled
            resolve(num);
            return;
        }
        // pending 狀態 到 rejected 
        reject(num);
    });
};
demo1(2)
    .then(
        // 處理 fulfilled 狀態
        resolveData => {
            console.log('resolve-', resolveData);
        },
        // 處理 rejected 狀態,以後的 catch 不會被觸發。
        rejectData => {
            console.log('reject', rejectData);
        }
    )
    .then(data => console.log('依然能夠調用哦,只是 data undefined', data));
    
    // 2 是偶數,狀態轉變 fulfilled 執行對應回調。
    // resolve- 2
    // 依然能夠調用哦,只是 data undefined undefined
複製代碼
  • demo2
const demo1 = function _demo1(num) {
    return new Promise((resolve, reject) => {
        if (num % 2 === 0) {
            resolve(num);
            return;
        }
        reject(num);
    });
};
demo1(1)
    .then(
        resolveData => {
            console.log('resolve-', resolveData);
        },
        rejectData => {
            console.log('reject', rejectData);
        }
    )
    .then(data => console.log('依然能夠調用哦,只是 data undefined-', data))
    .catch(error => console.log('catch:', error));
// 運行結果 1 爲偶數,then 處理了對應 rejected,狀態不會傳遞下去,不會執行 catch
// reject 1
//依然能夠調用哦,只是 data undefined- undefined
複製代碼
  • demo3
const demo1 = function _demo1(num) {
    return new Promise((resolve, reject) => {
        if (num % 2 === 0) {
            resolve(num);
            return;
        }
        reject(num);
    });
};
demo1(3)
    .then(resolveData => {
        console.log('resolve-', resolveData);
    })
    .then(data => console.log('依然能夠調用哦,只是 data undefined-', data))
    .catch(error => {
        console.log('catch-', error);
        return '我處理 rejected 了';
    })
    // 驗證 catch 攔截了 rejected ,並返回了 fulfilled 實例
    .then(data => console.log('catch 處理以後的 then-', data));
    // 3 爲奇數,狀態轉變爲 rejected ,而後 then 都沒有處理其狀態, rejected 一直被傳遞到 catch 處理。
    // catch- 3
    // catch 處理以後的 then- 我處理 rejected 了
複製代碼

推薦 then 只處理 fulfilled 狀態,catch 攔截 rejected 狀態處理邏輯學習

promise.then(resolve=>{}).then(resolve=>{}).then(resolve=>{}).catch(error=>{});
複製代碼

Promise 封裝 JQuery-Ajax

class AjaxUtil {
    static get(url, otherSetting = {}) {
        return new Promise((resolve, reject) => {
            const ajaxGetSetting = {
                url: url,
                type: 'GET',
                dataType: 'json',
                success: data => {
                    resolve(data);
                },
                error: error => {
                    reject(error);
                }
            };
            $.ajax(Object.assign(otherSetting, ajaxGetSetting));
        });
    }
}
// 不用再傳 callback ,爽歪歪了吧。
AjaxUtil.get('/api/videos').then(data => {
    document.getElementById('div').innerHTML = JSON.stringify(data);
});
複製代碼

Promise.all()

/** * const promise=Promise.all([promise,promise...]); * @description: Promise.all 返回 Promise 實例,數組裏面有一個狀態是 reject * 返回的實例的狀態爲 reject */
複製代碼
  • Demo
const sleep = function _sleep(ms) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
};
console.log('start');
const sleep1 = sleep(1000).then(() => {
    console.log('休息一秒');
    return '跟他説抱歉,我要去找個女孩';
});
const sleep2 = sleep(2000).then(() => {
    console.log('休息兩秒');
    return '忘記應該忘記的,面對將來一切能夠出現的。';
});
const sleep3 = sleep(3000).then(() => {
    console.log('休息三秒');
    return '孩子,生活不是靠書本和想固然來的,是靠心去體會的。';
});
const sleep4 = sleep(4000).then(() => {
    console.log('休息四秒');
    return '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。';
});
const ret2 = Promise.all([sleep1, sleep2, sleep3, sleep4]);
ret2.then(data => {
    // data 爲 全部 promise resolve 的 結果組成的數組
    console.log(data);
});
console.log('end');
複製代碼
// 執行結果爲
start
end
休息一秒
休息兩秒
休息三秒
休息四秒
[
  '跟他説抱歉,我要去找個女孩',
  '忘記應該忘記的,面對將來一切能夠出現的。',
  '孩子,生活不是靠書本和想固然來的,是靠心去體會的。',
  '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。'
]
複製代碼

Promise.all 能夠在想拿到多個異步事件執行結果的而後進行後續邏輯處理很方便。可是 Promise.all 不會阻塞後續代碼的執行。結合 async/await 用同步編碼的思惟編寫代碼很舒服。

Demo2

驗證 Promise.all 參數中的 promise 有一個狀態是 rejected 返回的 promise 是 rejected

const sleep = function _sleep(ms) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
};
const sleep1 = sleep(1000).then(() => {
    console.log('休息一秒');
    return '跟他説抱歉,我要去找個女孩';
});
const sleep2 = sleep(2000).then(() => {
    console.log('休息兩秒');
    return '忘記應該忘記的,面對將來一切能夠出現的。';
});
const sleep3 = sleep(3000).then(() => {
    console.log('休息三秒');
    return '孩子,生活不是靠書本和想固然來的,是靠心去體會的。';
});
const sleep4 = sleep(4000).then(() => {
    console.log('休息四秒');
    return '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。';
});
const sleep5 = sleep(5000).then(() => {
    console.log('休息五秒');
    throw new Error('模擬測試失敗狀況');
});
const ret2 = Promise.all([sleep1, sleep2, sleep3, sleep4, sleep5]);
ret2.then(data => {
    console.log(data);
    // 執行 catch
}).catch(error => {
    console.log(error.message);
});
複製代碼
休息一秒
休息兩秒
休息三秒
休息四秒
休息五秒
模擬測試失敗狀況
複製代碼

Promise.race([promise,promise])

最早改變狀態,就返回那個 promise

const sleep = function _sleep(ms) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, ms);
    });
};
const sleep2 = sleep(2000).then(() => {
    console.log('休息兩秒');
    return '你能夠了解世間萬物,但追根溯源的惟一途徑即是親身嘗試。';
});
const sleep3 = sleep(3000).then(() => {
    console.log('休息三秒');
    return '人終究是一個孤獨的個體,縱使你已經擁有了他人的懷抱,這其中,也許人與人之間惟一不一樣的,只是你把孤獨藏在哪裏。';
});
const sleep4 = sleep(4000).then(() => {
    console.log('休息四秒');
    return '躲避和不信任,是由於曾經被應該愛個人人遺棄。';
});
const ret = Promise.race([sleep2, sleep3, sleep4]);
ret.then(data => {
    console.log(data);
});
複製代碼
休息兩秒
你能夠了解世間萬物,但追根溯源的惟一途徑即是親身嘗試。
休息三秒
休息四秒
複製代碼

Async/await

async/await ,一直用一直爽。

async 函數返回一個 Promise實例。遇到 await 會代碼添加到微任務隊列,而後從函數中返回繼續執行後續代碼。

async/await 用於 Function 上。而且 await 不能單獨出現,須要和 async 一塊兒。

const async = async function _async() {
    // 執行遇到 await 將任務添加到微任務隊列,而後返回。繼續執行後續代碼
    const awaitRet = await Promise.resolve(1).then(data => {
        console.log('resolve-then');
        return data;
    });
    console.log(awaitRet);
    console.log('await 後面代碼');
};
console.log('start');
async();
console.log('end');
複製代碼
start
end
resolve-then
1
await 後面代碼
複製代碼
const awaitRet = await Promise.resolve(1).then(data => {
        console.log('resolve-then');
        return data;
    });
    console.log(awaitRet);
    console.log('await 後面代碼');
    // 上面代碼能夠簡單理解爲

Promise.resolve()
    .then(() => {
        const awaitRet = await Promise.resolve(1).then(data => {
        console.log('resolve-then');
        return data;
        });
    })
    // await 後面代碼添加到微任務隊列
    .then(() => {
        console.log(awaitRet);
        console.log('await 後面代碼');
    });
複製代碼

Demo1

const awaitFunc = function _awaitFunc() {
    return Promise.resolve('awaitFunc').then(data => {
        console.log(data);
        return 'awaitFunc-then-return-data';
    });
};
const async = async function _async() {
    // 等待 await 執行完以後,再將後續的代碼放入微任務隊列
    await awaitFunc().then(data => {
        console.log(data);
    });
    console.log('awaitFunc 執行完在打印');
};
async();
複製代碼
awaitFunc
awaitFunc-then-return-data
awaitFunc 執行完在打印
複製代碼

Demo2

await 阻塞的後續代碼放入微任務隊列?真的是這樣嗎?來個 demo 驗證個人想法。

const awaitFunc = function _awaitFunc() {
    return Promise.resolve('awaitFunc').then(data => {
        console.log(data);
        return 'awaitFunc-then-return-data';
    });
};
const async = async function _async() {
    // 等待 await 執行完以後,再將後續的代碼放入微任務隊列
    setTimeout(() => {
        console.log('驗證加入了微任務隊列---1');
    }, 0);
    await awaitFunc().then(data => {
        console.log(data);
        setTimeout(() => {
            console.log('驗證加入了微任務隊列---2');
        }, 0);
    });
    console.log('awaitFunc 執行完在打印');
};
async();

複製代碼
awaitFunc
awaitFunc-then-return-data
awaitFunc 執行完在打印
驗證加入了微任務隊列---1
驗證加入了微任務隊列---2
複製代碼

console.log('awaitFunc 執行完在打印') 被放入了宏任務隊列,那麼這段代碼不會比 setTimeout 先執行。所以 await 阻塞的後續代碼,放入到了當前事件循環的微任務隊列。全部的微任務也會在本次事件循環被執行完畢。

Promise&async/await 使用場景

用同步的思惟編寫異步的代碼,確實比較符合咱們的思惟習慣。

const sleep = function _sleep(ms, data) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`休息了 ${ms} 秒`);
            resolve(data);
        }, ms);
    });
};
console.log('start');

const data = [
    '跟他説抱歉,我要去找個女孩',
    '忘記應該忘記的,面對將來一切能夠出現的。',
    '孩子,生活不是靠書本和想固然來的,是靠心去體會的。',
    '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。'
];
const asyncFunc = function _async(data) {
    console.log('asyncFunc-start');
    const promises = [];
    for (let index = 0; index < data.length; index++) {
        promises.push(sleep(index, data[index]));
    }
    const ret = Promise.all(promises);
    console.log('處理完在開始');
    console.log('asyncFunc-end');
    return ret;
};
const retPromise = asyncFunc(data);
retPromise.then(data => {
    console.log(data);
});
console.log('end');

複製代碼
start
asyncFunc-start
處理完在開始
asyncFunc-end
end
休息了 0 秒
休息了 1 秒
休息了 2 秒
休息了 3 秒
[
  '跟他説抱歉,我要去找個女孩',
  '忘記應該忘記的,面對將來一切能夠出現的。',
  '孩子,生活不是靠書本和想固然來的,是靠心去體會的。',
  '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。'
]
複製代碼

上述代碼呢我想讓 asyncFunc 裏面順序執行,先處理 Promise.all ,再繼續 await 後面的代碼。

  • 使用 async/await 改寫。
const sleep = function _sleep(ms, data) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`休息了 ${ms} 秒`);
            resolve(data);
        }, ms);
    });
};
console.log('start');

const data = [
    '跟他説抱歉,我要去找個女孩',
    '忘記應該忘記的,面對將來一切能夠出現的。',
    '孩子,生活不是靠書本和想固然來的,是靠心去體會的。',
    '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。'
];
const asyncFunc = async function _async(data) {
    console.log('asyncFunc-start');
    const promises = [];
    for (let index = 0; index < data.length; index++) {
        promises.push(sleep(index, data[index]));
    }
    const ret = await Promise.all(promises);
    console.log('處理完在開始');
    console.log('asyncFunc-end');
    return ret;
};
const retPromise = asyncFunc(data);
retPromise.then(data => {
    console.log(data);
});
console.log('end');

複製代碼
start
asyncFunc-start
end
休息了 0 秒
休息了 1 秒
休息了 2 秒
休息了 3 秒
處理完在開始
asyncFunc-end
[
  '跟他説抱歉,我要去找個女孩',
  '忘記應該忘記的,面對將來一切能夠出現的。',
  '孩子,生活不是靠書本和想固然來的,是靠心去體會的。',
  '總有人有很差的時候,但這也會讓你回憶起從前未曾在乎的美好。'
]
複製代碼

能夠看到 await 後面的代碼須要等到 Promise.all 微任務所有執行完畢才能夠開始。注意那個 end 打印時機,其實 async/await 也是基於事件循環,並非阻塞代碼執行。否則那個 end 應該最後打印纔是。

在循環中使用 async/await

forof,forin,fori 循環能夠感知 await

const data = [1, 2, 3];
const async = async function _async(data) {
    for (const index in data) {
        const item = data[index];
        const ret = await Promise.resolve(item)
            .then(data => {
                console.log('第一個 then');
                return data;
            })
            .then(data => {
                console.log('第二個 then');
                return data;
            });
        console.log('await 返回值', ret);
        console.log('await 以後的代碼');
    }
};
async(data);

複製代碼
// fori
const data = [1, 2, 3];
const async = async function _async(data) {
    for (let index = 0; index < data.length; index++) {
        const item = data[index];
        await Promise.resolve(item)
            .then(data => {
                console.log('第一個 then');
                return data;
            })
            .then(data => {
                console.log('第二個 then');
                return data;
            });
        console.log('await 以後的代碼');
    }
};
async(data);

複製代碼
// forof
const data = [1, 2, 3];
const async = async function _async(data) {
    for (const item of data) {
        await Promise.resolve(item)
            .then(data => {
                console.log('第一個 then');
                return data;
            })
            .then(data => {
                console.log('第二個 then');
                return data;
            });
        console.log('await 以後的代碼');
    }
};
async(data);
複製代碼
第一個 then
第二個 then
await 以後的代碼
第一個 then
第二個 then
await 以後的代碼
第一個 then
第二個 then
await 以後的代碼
複製代碼

forin,forof,fori 都能感知 await

forEach 不能感知 await

const data = [1, 2, 3];
const async = async function _async(data) {
    data.forEach(async item => {
        const ret = await Promise.resolve(item)
            .then(data => {
                console.log('第一個 then');
                return data;
            })
            .then(data => {
                console.log('第二個 then');
                return data;
            });
        console.log('await 返回值', ret);
        console.log('await 以後的代碼');
    });
};
async(data);
複製代碼
第一個 then
第一個 then
第一個 then
第二個 then
第二個 then
第二個 then
await 返回值 1
await 以後的代碼
await 返回值 2
await 以後的代碼
await 返回值 3
await 以後的代碼
複製代碼
相關文章
相關標籤/搜索