模塊一:函數(shù)式編程與 JS 異步編程、手寫 Promise參考答案
簡(jiǎn)答題
一、談?wù)勀闶侨绾卫斫?JS 異步編程的,EventLoop、消息隊(duì)列都是做什么的,什么是宏任務(wù),什么是微任務(wù)?
-
JS 異步編程
JavaScript 語言的執(zhí)行環(huán)境是單線程的,一次只能執(zhí)行一個(gè)任務(wù),多任務(wù)需要排隊(duì)等候,這種模式可能會(huì)阻塞代碼,導(dǎo)致代碼執(zhí)行效率低下。為了避免這個(gè)問題,出現(xiàn)了異步編程。一般是通過 callback 回調(diào)函數(shù)、事件發(fā)布/訂閱、Promise 等來組織代碼,本質(zhì)都是通過回調(diào)函數(shù)來實(shí)現(xiàn)異步代碼的存放與執(zhí)行。
-
EventLoop 事件環(huán)和消息隊(duì)列
EventLoop 是一種循環(huán)機(jī)制 ,不斷去輪詢一些隊(duì)列 ,從中找到 需要執(zhí)行的任務(wù)并按順序執(zhí)行的一個(gè)執(zhí)行模型。
消息隊(duì)列 是用來存放宏任務(wù)的隊(duì)列, 比如定時(shí)器時(shí)間到了, 定時(shí)間內(nèi)傳入的方法引用會(huì)存到該隊(duì)列, ajax回調(diào)之后的執(zhí)行方法也會(huì)存到該隊(duì)列。
EventLoop.jpg
一開始整個(gè)腳本作為一個(gè)宏任務(wù)執(zhí)行。執(zhí)行過程中同步代碼直接執(zhí)行,宏任務(wù)等待時(shí)間到達(dá)或者成功后,將方法的回調(diào)放入宏任務(wù)隊(duì)列中,微任務(wù)進(jìn)入微任務(wù)隊(duì)列。
當(dāng)前主線程的宏任務(wù)執(zhí)行完出隊(duì),檢查并清空微任務(wù)隊(duì)列。接著執(zhí)行瀏覽器 UI 線程的渲染工作,檢查web worder 任務(wù),有則執(zhí)行。
然后再取出一個(gè)宏任務(wù)執(zhí)行。以此循環(huán)...
-
宏任務(wù)與微任務(wù)
宏任務(wù)可以理解為每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)。
瀏覽器為了讓 JS 內(nèi)部宏任務(wù) 與 DOM 操作能夠有序的執(zhí)行,會(huì)在一個(gè)宏任務(wù)執(zhí)行結(jié)束后,在下一個(gè)宏任務(wù)執(zhí)行開始前,對(duì)頁面進(jìn)行重新渲染。
宏任務(wù)包含:script(整體代碼)、setTimeout、setInterval、I/O、UI交互事件、MessageChannel 等
微任務(wù)可以理解是在當(dāng)前任務(wù)執(zhí)行結(jié)束后需要立即執(zhí)行的任務(wù)。也就是說,在當(dāng)前任務(wù)后,在渲染之前,執(zhí)行清空微任務(wù)。
所以它的響應(yīng)速度相比宏任務(wù)會(huì)更快,因?yàn)闊o需等待 UI 渲染。
微任務(wù)包含:Promise.then、MutaionObserver、process.nextTick(Node.js 環(huán)境)等
代碼題
一、將下面異步代碼使用 Promise 的方式改進(jìn)
setTimeout(function() {
var a = 'hello'
setTimeout(function() {
var b = 'lagou'
setTimeout(function() {
var c = 'I ?? U'
console.log(a + b + c)
}, 10);
}, 10);
}, 10);
參考代碼:
new Promise(resolve => {
var a = 'hello'
resolve(a)
}).then(resA => {
var b = 'lagou'
return resA + b;
}).then(resB => {
var c = 'I ? U'
console.log(resB + c)
})
//
async function showStr() {
let a = await Promise.resolve('helloP')
let b = await Promise.resolve('lagou')
let c = await Promise.resolve('IU')
console.log(a + b + c)
}
showStr()
--------------------------------------------------
/ function promise(str) {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve(str)
// }, 10)
// })
// }
// async function showStr() {
// let a = await promise('hello')
// let b = await promise('lagou')
// let c = await promise('IU')
// console.log(a + b + c)
// }
// showStr()
-----------------------------
Promise.resolve('hello')
.then((value) => {
return value + 'logou';
})
.then((value) => {
return value + 'I ? U';
})
.then((value) => console.log(value));
二、基于以下代碼完成下面的四個(gè)練習(xí)
const fp = require('lodash/fp')
// 數(shù)據(jù):horsepower 馬力,dollar_value 價(jià)格,in_stock 庫存
const cars = [
{ name: 'Ferrari FF', horsepower: 660, dollar_value: 700000, in_stock: true },
{ name: 'Spyker C12 Zagato', horsepower: 650, dollar_value: 648000, in_stock: false },
{ name: 'Jaguar XKR-S', horsepower: 550, dollar_value: 132000, in_stock: false },
{ name: 'Audi R8', horsepower: 525, dollar_value: 114200, in_stock: false },
{ name: 'Aston Martin One-77', horsepower: 750, dollar_value: 1850000, in_stock: true },
{ name: 'Pagani Huayra', horsepower: 700, dollar_value: 1300000, in_stock: false }
]
練習(xí)1:使用組合函數(shù) fp.flowRight() 重新實(shí)現(xiàn)下面這個(gè)函數(shù)
let isLastInStock = function(cars){
// 獲取最后一條數(shù)據(jù)
let last_car = fp.last(cars)
// 獲取最后一條數(shù)據(jù)的 in_stock 屬性值
return fp.prop('in_stock', last_car)
}
先定義獲取最后一條數(shù)據(jù)的函數(shù),再定義獲取某個(gè)對(duì)象中的 in_stock 屬性的函數(shù),再用 fp.flowRight 組合函數(shù)
let isLastInStock = fp.flowRight(fp.prop('in_stock'), fp.last);
console.log(isLastInStock(cars)); // false
練習(xí)2:使用 fp.flowRight()、fp.prop() 和 fp.first() 獲取第一個(gè) car 的 name
先定義獲取第一條數(shù)據(jù)的函數(shù),再定義獲取某個(gè)對(duì)象中的 name 屬性的函數(shù),再用 fp.flowRight 組合函數(shù)
const getFirstName = fp.flowRight(fp.prop("name"), fp.first)
console.log(getFirstName(cars)) // Ferrari FF
練習(xí)3:使用幫助函數(shù) _average 重構(gòu) averageDollarValue,使用函數(shù)組合的方式實(shí)現(xiàn)
let _average = function(xs){
return fp.reduce(fp.add, 0, xs) / xs.length
}
先定義獲取某個(gè)對(duì)象中的 dollar_value 屬性的函數(shù),將該函數(shù)作為 fp.map 的數(shù)組元素處理函數(shù),再用 fp.flowRight 組合函數(shù)
let averageDollarValue = fp.flowRight(_average, fp.map('dollar_value'));
console.log(averageDollarValue(cars)); //790700
練習(xí)4:使用 flowRight 寫一個(gè) sanitizeNames() 函數(shù),返回一個(gè)下劃線連續(xù)的小寫字符串,把數(shù)組中的 name 轉(zhuǎn)換為這種形式,例如:sanitizeNames(["Hello World"]) => ["hello_world"]
let _underscore = fp.replace(/\W+/g, '_') // 無須改動(dòng),并在 sanitizeNames 中使用它
先定義獲取某個(gè)對(duì)象中的 name 屬性的函數(shù),再定義轉(zhuǎn)化為小寫的函數(shù),再將空格和下劃線替換,,再用 fp.flowRight 組合函數(shù)
let sanitizeNames = fp.flowRight(
fp.map(_underscore),
fp.map(fp.toLower),
fp.map((car) => car.name)
);
console.log(sanitizeNames(CARS))
// [
// 'ferrari_ff',
// 'spyker_c12_zagato',
// 'jaguar_xkr_s',
// 'audi_r8',
// 'aston_martin_one_77',
// 'pagani_huayra'
// ]
三、基于下面提供的代碼,完成后續(xù)的四個(gè)練習(xí)
// support.js
class Container {
static of(value){
return new Container(value)
}
constructor(value){
this._value = value
}
map(fn){
return Container.of(fn(this._value))
}
}
class Maybe {
static of(x){
return new Maybe(x)
}
isNothing(){
return this._value === null || this._value === undefined
}
constructor(x){
this._value = x
}
map(fn){
return this.isNothing() ? this : Maybe.of(fn(this._value))
}
}
module.exports = { Maybe, Container }
練習(xí)1:使用 fp.add(x, y) 和 fp.map(f,x) 創(chuàng)建一個(gè)能讓 functor 里的值增加的函數(shù) ex1
const fp = require('lodash/fp')
const {Maybe, Container} = require('./support')
let maybe = Maybe.of([5,6,1])
let ex1 = () => {
// 你需要實(shí)現(xiàn)的函數(shù)。。。
}
函子對(duì)象的 map 方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理,函數(shù)的參數(shù)為傳入 of 方法的參數(shù);接著對(duì)傳入的整個(gè)數(shù)組進(jìn)行遍歷,并對(duì)每一項(xiàng)執(zhí)行 fp.add 方法
let ex1 = maybe.map(i => fp.map(fp.add(1), i))
console.log(ex1) // [6, 7, 2]
練習(xí)2:實(shí)現(xiàn)一個(gè)函數(shù) ex2,能夠使用 fp.first 獲取列表的第一個(gè)元素
const fp = require('lodash/fp')
const {Maybe, Container} = require('./support')
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = () => {
// 你需要實(shí)現(xiàn)的函數(shù)。。。
}
解答如下:
let ex2 = xs.map(i => fp.first(i))
console.log(ex2)// do
練習(xí)3:實(shí)現(xiàn)一個(gè)函數(shù) ex3,使用 safeProp 和 fp.first 找到 user 的名字的首字母
const fp = require('lodash/fp')
const {Maybe, Container} = require('./support')
let safeProp = fp.curry(function(x, o){
return Maybe.of(o[x])
})
let user = { id: 2, name: 'Albert' }
let ex3 = () => {
// 你需要實(shí)現(xiàn)的函數(shù)。。。
}
調(diào)用 ex3 函數(shù)傳入 user 對(duì)象,safeProp 是經(jīng)過柯里化處理的,可以先傳“屬性”參數(shù),后傳“對(duì)象”參數(shù)。safeProp 函數(shù)處理后返回 user 的值,再調(diào)用fp.first 獲取首字母
let ex3 = fp.flowRight(fp.map(i => fp.first(i)), safeProp('name'))
console.log(ex3(user)) // A
// 或者 return safeProp("name", user).map(x => fp.first(x));
練習(xí)4:使用 Maybe 重寫 ex4,不要有 if 語句
const fp = require('lodash/fp')
const {Maybe, Container} = require('./support')
let ex4 = function(n){
if(n){
return parseInt(n)
}
}
MayBe 函子用來處理外部的空值情況,防止空值的異常,拿到函子的值之后進(jìn)行 parseInt 轉(zhuǎn)化
let ex4 = n => Maybe.of(n).map(parseInt)
console.log(ex4('1')) // 1
四、手寫實(shí)現(xiàn) MyPromise 源碼
要求:盡可能還原 Promise 中的每一個(gè) API,并通過注釋的方式描述思路和原理?!緟⒖即a】
// 初始狀態(tài)
const PENDING = "pending";
// 完成狀態(tài)
const FULFILLED = "fulfilled";
// 失敗狀態(tài)
const REJECTED = "rejected";
// 異步執(zhí)行方法封裝
function asyncExecFun(fn) {
setTimeout(() => fn(), 0);
}
// 執(zhí)行promise resolve功能
function resolvePromise(promise, res, resolve, reject) {
// 返回同一個(gè)promise
if (promise === res) {
reject(new TypeError("Chaining cycle detected for promise #<MyPromise>"));
return;
}
// promise結(jié)果
if (res instanceof MyPromise) {
res.then(resolve, reject);
} else {
// 非promise結(jié)果
resolve(res);
}
}
/**
* 1. 是個(gè)構(gòu)造函數(shù)
* 2. 傳入一個(gè)可執(zhí)行函數(shù) 函數(shù)的入?yún)⒌谝粋€(gè)為 fullFill函數(shù) 第二個(gè)為 reject函數(shù); 函數(shù)立即執(zhí)行, 參數(shù)函數(shù)異步執(zhí)行
* 3. 狀態(tài)一旦更改就不可以變更 只能 pending => fulfilled 或者 pending => rejected
* 4. then 的時(shí)候要處理入?yún)⒌那闆r successCallback 和failCallback 均可能為非函數(shù)
* 默認(rèn)的 failCallback 一定要將異常拋出, 這樣下一個(gè)promise便可將其捕獲 異常冒泡的目的
* 5. then 中執(zhí)行回調(diào)的時(shí)候要捕獲異常 將其傳給下一個(gè)promise
* 如果promise狀態(tài)未變更 則將回調(diào)方法添加到對(duì)應(yīng)隊(duì)列中
* 如果promise狀態(tài)已經(jīng)變更 需要異步處理成功或者失敗回調(diào)
* 因?yàn)榭赡艹霈F(xiàn) 回調(diào)結(jié)果和當(dāng)前then返回的Promise一致 從而導(dǎo)致死循環(huán)問題
* 6. catch只是then的一種特殊的寫法 方便理解和使用
* 7. finally 特點(diǎn) 1. 不過resolve或者reject都會(huì)執(zhí)行
* 2. 回調(diào)沒有參數(shù)
* 3. 返回一個(gè)Promise 且值可以穿透到下一個(gè)then或者catch
* 8. Promise.resolve, Promise.reject 根據(jù)其參數(shù)返回對(duì)應(yīng)的值 或者狀態(tài)的Promise即可
* 9. Proise.all 特點(diǎn) 1. 返回一個(gè)Promise
* 2. 入?yún)⑹菙?shù)組 resolve的情況下出參也是數(shù)組 且結(jié)果順序和調(diào)用順序一致
* 3. 所有的值或者promise都完成才能resolve 所有要計(jì)數(shù)
* 4. 只要有一個(gè)為reject 返回的Promise便reject
* 10. Proise.race 特點(diǎn) 1. 返回一個(gè)Promise
* 2. 入?yún)⑹菙?shù)組 那么出參根據(jù)第一個(gè)成功或者失敗的參數(shù)來確定
* 3. 只要有一個(gè)resolve 或者reject 便更改返回Promise的狀態(tài)
*
*
*/
class MyPromise {
status = PENDING;
value = undefined;
reason = undefined;
successCallbacks = [];
failCallbacks = [];
constructor(exector) {
// 立即執(zhí)行傳入?yún)?shù)
// 參數(shù)直接寫為 this.resolve 會(huì)導(dǎo)致函數(shù)內(nèi) this指向會(huì)發(fā)生改變
// 異步執(zhí)行狀態(tài)變更
// 捕獲執(zhí)行器的異常
try {
exector(
(value) => asyncExecFun(() => this.resolve(value)),
(reason) => asyncExecFun(() => this.reject(reason))
);
} catch (e) {
this.reject(e)
}
}
resolve(value) {
// 如果狀態(tài)已經(jīng)變更則直接返回
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
// 執(zhí)行所有成功回調(diào)
while (this.successCallbacks.length) this.successCallbacks.shift()();
}
reject(reason) {
// 如果狀態(tài)已經(jīng)變更則直接返回
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
if(!this.failCallbacks.length){
throw '(in MyPromise)'
}
// 執(zhí)行所有失敗回調(diào)
while (this.failCallbacks.length) this.failCallbacks.shift()();
}
then(successCallback, failCallback) {
// 成功函數(shù)處理 忽略函數(shù)之外的其他值
successCallback =
typeof successCallback == "function" ? successCallback : (v) => v;
// 失敗函數(shù)處理 忽略函數(shù)之外的其他值 拋出異常 實(shí)現(xiàn)catch冒泡的關(guān)鍵
failCallback =
typeof failCallback == "function"
? failCallback
: (reason) => {
throw reason;
};
let promise = new MyPromise((resolve, reject) => {
// 統(tǒng)一異常處理邏輯
const execFun = (fn, val) => {
try {
let res = fn(val);
resolvePromise(promise, res, resolve, reject);
} catch (e) {
reject(e);
}
};
// 執(zhí)行成功回調(diào)
const execSuccessCallback = () => execFun(successCallback, this.value);
// 執(zhí)行失敗回調(diào)
const execFailCallback = () => execFun(failCallback, this.reason);
// 同步將對(duì)應(yīng)成功或者失敗回調(diào)事件加入對(duì)應(yīng)回調(diào)隊(duì)列
if (this.status === PENDING) {
// 將成功回調(diào)加入隊(duì)列
this.successCallbacks.push(execSuccessCallback);
// 講失敗回調(diào)加入隊(duì)列
this.failCallbacks.push(execFailCallback);
return;
}
// 延遲執(zhí)行 可以將函數(shù)執(zhí)行結(jié)果和當(dāng)前then 返回的promise 進(jìn)行比較
asyncExecFun(() => {
// 如果已經(jīng) fulfilled 可直接調(diào)用成功回調(diào)方法
if (this.status === FULFILLED) {
execSuccessCallback();
// 如果已經(jīng) rejected 可直接調(diào)用失敗回調(diào)方法
} else if (this.status === REJECTED) {
execFailCallback();
}
});
});
return promise;
}
catch(failCallback) {
return this.then(undefined, failCallback);
}
finally(callback) {
return this.then(
// 穿透正常值
(value) => MyPromise.resolve(callback()).then(() => value),
(reason) =>
MyPromise.resolve(callback()).then(() => {
// 穿透異常信息
throw reason;
})
);
}
static resolve(value) {
// 如果是MyPromise 實(shí)例 則直接返回
if (value instanceof MyPromise) return value;
// 如果是MyPromise 實(shí)例 否則返回一個(gè) MyPromise實(shí)例
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
// 如果是MyPromise 實(shí)例 則直接返回
if (reason instanceof MyPromise) return reason;
// 如果是MyPromise 實(shí)例 否則返回一個(gè) MyPromise實(shí)例
return new MyPromise((resolve, reject) => reject(reason));
}
// all方法
static all(array) {
// 存儲(chǔ)結(jié)果
let result = [];
// 存儲(chǔ)數(shù)組長(zhǎng)度
let len = array.length;
// 創(chuàng)建返回MyPromise
let promise = new MyPromise((resolve, reject) => {
// 定義當(dāng)前MyPromise的索引
let index = 0;
// 添加數(shù)據(jù)的公用方法
function addData(key, data) {
// 賦值
result[key] = data;
// 索引遞增
index++;
// 全部執(zhí)行完則resolve
if (index == len) {
resolve(result);
}
}
// 按順序變量數(shù)組
for (let i = 0; i < len; i++) {
let curr = array[i];
// 如果是MyPromise則 按其規(guī)則處理
if (curr instanceof MyPromise) {
curr.then((value) => addData(i, value), reject);
} else {
// 非MyPromise直接賦值
addData(i, curr);
}
}
});
// 返回新的MyPromise實(shí)例
return promise;
}
// 只要有一個(gè)成功或者失敗就返回
static race(array) {
let promise = new MyPromise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
let curr = array[i];
// MyPromise實(shí)例 結(jié)果處理
if (curr instanceof MyPromise) {
curr.then(resolve, reject);
} else {
// 非MyPromise實(shí)例處理
resolve(curr);
}
}
});
return promise;
}
}
module.exports = MyPromise;
