1-1作業(yè)參考答案

模塊一:函數(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;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容