模块一:函数式编程与js异步编程、手写Promise

简答题

一、谈谈你是如何理解js异步编程的,EventLoop、消息队列都是做什么的,什么是宏任务,什么是微任务?

  • js异步编程理解:因为javascript语言的执行环境是单线程的,代码的执行顺序是从上至下的,如果有多个任务,必须要前面一个任务完成才能继续执行下一个任务,这样的话,如果中间有某一个任务比较耗时就会阻塞主进程。使用异步编程可以解决多个任务的阻塞问题,异步任务把一个任务分成两个阶段,先执行第一阶段,然后继续执行后面的任务,等做好了准备,再回过头执行第二段,排在异步任务后面的代码,不用等异步任务执行结束就会马上运行。
  • EventLoop、消息队列:javascript主线程遇到异步任务触发之后,由事件触发线程将异步对应的回调函数加入到消息队列中,消息队列中的回调函数等待被执行。javascript的主线程不断的循环往复的从任务队列任务中读取任务,执行任务,这种运行机制称为事件循环,也就是EventLoop。
  • 宏任务:macro-task,像主代码块、setTimeout、setInterval等都属于宏任务,可以理解是每次执行栈执行的代码就是一个宏任务,包括每次从事件队列中获取一个事件回调并放到执行栈中执行的任务。
  • 微任务:micro-task,是在当前task执行结束后立即执行的任务。也就是说,在当前macro-task任务后,下一个macro-task之前执行。响应速度比setTimeout更快,因为无需等待渲染。可以理解为,在某一个macro-task执行完后,就会将在它执行期间产生的所有micro-task都执行完毕(在渲染前)。

代码题

一、将下面异步代码使用Promise的方式改进

setTimeout(function() {
  var a = 'hello'
  setTimeout(function() {
    var b = 'lagou'
    setTimeout(function () {
      var c = 'i ❤️ U'
      console.log(a + b + c)
    }, 10);
  }, 10);
}, 10)

改进后

function p1 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('hello')
    }, 10);
  })
}
function p2 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('lagou')
    }, 10);
  })
}
function p3 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('i ❤️ U')
    }, 10);
  })
}

Promise.all([p1(), p2(), p3()]).then(res => {
  console.log(res.join(' '))
})

二、基于以下代码完成下面的四个练习

const fp = require('lodash/fp')
// 数据
// horsepower 马力,dollar_value 价格,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}
]

答题

// 练习一:使用函数组合fp.flowRight()重新实现下面这个函数
const lastCarInStock = fp.flowRight(fp.prop('in_stock'), fp.last)
console.log(lastCarInStock(cars))

// 练习二:使用fp.flowRight()、fp.prop()和fp.first()获取第一个car的name
const getFirstCarName = fp.flowRight(fp.prop('name'), fp.first)
console.log(getFirstCarName(cars))

// 练习三:使用帮助函数_average重构averageDollarValue,使用函数组合的方式实现
let _average = function (xs) {
  return fp.reduce(fp.add, 0, xs) / xs.length
}

let averageDollarValue = fp.flowRight(_average ,fp.map(car => car.dollar_value))
console.log('averageDollarValue: ', averageDollarValue(cars))

const trace = fp.curry((tag, v) => {
  console.log(tag, v)
  return v
})
// 练习四:使用flowRight写一个sanitizeNames()函数,返回一个下划线链接的小写字符串,把数组中的name转换成为这种形式:
// 例如:sanitizeNames(["Hello World"]) => ["hello_world"]
let _underscore = fp.replace(/\W+/g, '_')
function sanitizeNames (arr) {
  return fp.flowRight(fp.map(_underscore), fp.map(fp.lowerCase))(arr)
}

console.log(sanitizeNames(["Hello World"]))

三、基于下面提供的代码,完成后续的四个练习

// 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 }

答题

const fp = require('lodash/fp')
const { MayBe, Container } = require('./support')
let maybe = MayBe.of([5, 6, 1])

// 练习1:使用fp.add(x, y)和fp.map(f, x)创建一个能让functor里的值增加的函数ex1
let ex1 = () => {
  return maybe.map(fp.map(fp.add(2)))
}

console.log(ex1())
// 练习2:实现一个函数ex2,能够使用fp.first获取列表的第一个元素
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])

let ex2 = () => {
  return xs.map(fp.first)
}
console.log(ex2())

// 练习3:实现一个函数ex3,使用safeProp和fp.first找到user的名字的首字母
let safeProp = fp.curry(function(x, o) {
  return MayBe.of(o[x])
})
let user = { id: 2, name: 'Albert' }
let ex3 = () => {
  return fp.first(safeProp('name')(user)._value)
}

console.log(ex3())

// 练习4:使用Maybe重写ex4,不要有if语句
let ex4 = function (n) {
  return MayBe.of(n).map(parseInt)
}
console.log(ex4('12')._value)

四、手写实现MyPromise源码

要求:尽可能还原promise中的每一个API,并通过注释的方式描述思路和原理。
答题

/**
 * 1.Promise就是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
 * 2.Promise中有三个状态,分别为:fulfilled,rejected,pending
 *    pending -> fulfilled
 *    pending -> rejected
 *    一旦状态确定就不可更改
 * 3.resolve和reject函数是用来更改状态的
 *    resolve: fulfilled
 *    reject: rejected
 * 4.then方法内部做的事情就是判断状态,如果状态是成功,调用成功回调,失败调用失败回调
 * 5.then成功回调有一个参数,表示成功之后的值,失败有一个参数,表示失败后的原因
 * 6.同一个promise对象下面的then方法是可以被调用多次的
 * 7.then方法是可以被链式调用的,后面then方法的回调函数拿到值是上一个then方法的回调函数的返回值
 */
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
const PENDING = 'pending'

class MyPromise {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCallback = []
  failCallback = []

  constructor(executor) {
    try {
      // 立即执行执行器
      executor(this.resolve, this.reject)
    } catch (error) {
      // 捕获执行器异常
      this.reject(error)
    }
  }

  resolve = (value) => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // 从数组中取成功回调函数调用,每次调用完就移除该函数
    while (this.successCallback.length) {
      this.successCallback.shift()()
    }
  }

  reject = (reason) => {
    // 如果不是等待,阻止程序向下执行
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    // 从数组中取失败回调函数调用,每次调用完就移除该函数
    while (this.failCallback.length) {
      this.failCallback.shift()()
    }
  }

  then = (successCallback, failCallback) => {
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : reason => { throw reason}
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const x = successCallback(this.value)
            // 判断x的值是普通值还是promise对象
            // 如果是普通值,直接调用resolve
            // 如果是promise对象,查看promise对象的返回结果
            // 再根据promise对象返回的结果,决定调用resolve,还是调用reject
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)

      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = failCallback(this.reason)
            // 判断x的值是普通值还是promise对象
            // 如果是普通值,直接调用resolve
            // 如果是promise对象,查看promise对象的返回结果
            // 再根据promise对象返回的结果,决定调用resolve,还是调用reject
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        // 等待状态
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              const x = successCallback(this.value)
              // 判断x的值是普通值还是promise对象
              // 如果是普通值,直接调用resolve
              // 如果是promise对象,查看promise对象的返回结果
              // 再根据promise对象返回的结果,决定调用resolve,还是调用reject
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              const x = failCallback(this.reason)
              // 判断x的值是普通值还是promise对象
              // 如果是普通值,直接调用resolve
              // 如果是promise对象,查看promise对象的返回结果
              // 再根据promise对象返回的结果,决定调用resolve,还是调用reject
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise2
  }

  finally (callback) {
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
      return MyPromise.resolve(callback()).then(() =>
        {throw reason})
      
    })
  }

  catch (failCallback) {
    return this.then(undefined, failCallback)
  }

  static all (array) {
    const result = []
    let index = 0
    
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value
        index++
        if (index === array.length) {
          resolve(result)
        }
      }
      for (let i = 0; i < array.length; i++) {
        const element = array[i];
        if (element instanceof MyPromise) {
          element.then(value => addData(i, value), reason => reject(reason))
        } else {
          addData(i, element)
        }
      }
    })
  }

  static resolve (value) {
    if (value instanceof MyPromise) {
      return value
    }
    return new MyPromise(resolve => resolve(value))
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

module.exports = MyPromise
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容