前端小知识第一弹 - 包含手写Promise

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

说到 Javascript (以下称之为JS)异步编程,那么有必要说一下JS 运行机制,作为一门脚本语言,JS的主要用途是与用户交互以及操作DOM。而为了避免多线程同时操作带来的复杂性,决定了它只能是单线程。
那么,如果是单线程就意味着,如果在同一时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。但是,如果前一个任务的执行时间很长,比如文件读取或者ajax操作·,后一个任务就必须等待着,直到所有数据获取完成才能继续操作,这样会出现卡顿,甚至卡死情况,严重影响用户体验。
因此,JS在设计的时候就意识到这个问题,于是将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步运行机制如下四步:

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个“任务队列”。只要异步任务有了运行结果,就在任务队列中放置一个事件。
  3. 一旦“执行栈”中所有同步任务执行完毕,系统就会读取“任务队列”,查看里面有哪些事件。于是那些对应的异步任务,结束等待状态,进入“执行栈”,开始执行。
  4. 主线程不断重复上面的第三部。
EventLoop(事件循环)

只要栈中的代码执行完毕,主线程就会从“任务队列”中读取事件,依次执行那些对应的回调函数。这个过程是循环不断的,所以整个的这种运行机制就被称为EventLoop(事件循环)

宏任务与微任务

例如:去银行排队办理业务的人就是一个个宏任务,当第一个P1(第一个人)办理业务时,其他所有人都在等待,当P1办理完业务结束时,柜员会询问P1还有没有其他微任务。如果P1还有办卡(微任务),那么其他排队的人(其他宏任务)还需继续等待,直到P1,把所有微任务办完。
宏任务包括:script(整体代码)、setTimeout、setInterval、。。。等
微任务包括: Promise、Object.observe。。。等
宏任务、微任务执行顺序:
先执行同步代码,遇到异步宏任务则将其放入宏任务队列中,遇到异步微任务,则将其放入微任务队列中,当所有同步代码执行完毕后,在将异步微任务从队列中调入主线程执行。当微任务执行完毕后再将异步宏任务从队列中调入主线程,一直循环直至所有任务执行完毕。

二 动手实操收获更多

2.1 将下面异步代码使用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 prom (text) {
    return new Promise((resolve, reject)=> {
        setTimeout(function () {
            resolve(text)
        },1000)
    })
}
const prom1 = prom('hello')
const prom2 = prom('lagou')
const prom3 = prom('I ❤  U')
Promise.all([prom1, prom2, prom3]).then(values => {
    console.log(values[0]+ values[1] + values[2])
})

2.2 基于以下代码完成下面四个练习

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}
]
练习1:使用函数组合fp.flowRight()重新实现下面这个函数
// 答案
let isLastInStock = function(cars) {
    // 获取最后一条数据
    let last_car = fp.last(cars)
    // 获取最后一条数据的 in_stock 属性值
    return fp.prop('in_stock', last_car)
}
const lastCar = fp.flowRight(fp.prop('in_stock'), fp.last)
console.log(lastCar(cars))
练习2: 使用 fp.flowRight()、 fp.prop()、 fp.first()获取第一个 car 的name
//答案
const getFirstCar = fp.flowRight(fp.prop('name'), fp.first)
console.log(getFirstCar(cars))
练习3: 使用帮助函数 _average 重构 averageDollarValue, 使用函数组合的方式实现
let _average = function(xs) {
    console.log('xs', xs)
   return fp.reduce(fp.add, 0, xs) / xs.length
} // 无需改动
let averageDollarValue = function (cars) {
     let dollar_values = fp.map(function(car) {
         return car.dollar_value
     }, cars)
     console.log('dollar_values', dollar_values)
    return _average(dollar_values)
}
//答案
let averageDollarValue = fp.flowRight(_average,fp.map(fp.prop('dollar_value')))
练习4: 使用 flowRight 写一个sanitizeNames() 函数,返回一个下划线连接的小写字符串,把数组中的 name 转换为这种形式: 例如:sanitizeNames([“Hello World”]) => [“hello_world”]
// 答案:
let _underscore = fp.replace(/\W+/g, '_') // 无需改动, 并在 sanitizeNames 中使用它
let sanitizeNames = fp.flowRight(fp.split(','),fp.toLower,fp.map(fp.flowRight(_underscore,fp.prop('name'))))
console.log(sanitizeNames(cars))

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

// support.js

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

练习1: 使用 fp.add(x, y) 和fp.map(f,x) 创建一个能让 functor 里的值增加的函数 ex1

// app.js

//答案
// app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
let maybe = Maybe.of([5,6,1])
let ex1 = ()=> {
    //需要执行的函数
    return maybe.map((x) => fp.map(fp.add(1), x))._value;
}
console.log(ex1());

练习2: 实现一个函数 ex2, 能够使用 fp.first 获取列表的第一个元素

// app.js

//答案
//app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = () => {
    // 需要实现的函数
    return fp.first(xs._value)
}
console.log(ex2())

练习3: 实现一个函数 ex3 ,使用safeProp 和 fp.first 找到 user 的名字2的首字母

// app.js

// app.js
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 = ()=> {
    // 需要实现的函数
    return fp.first(safeProp('name', user)._value)
}
console.log(ex3()) // A

练习4: 使用Maybe 重写 ex4, 不要有 if 语句

app.js

// app.js
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')
// let ex4 = function (n) {
//     if (n) {
//         return parseInt(n)
//     }
// }
let ex4 = Maybe.of()
    .map(n => parseInt(n))
console.log(ex4._value)
// '5' -> 5 
// 'a' -> NAN

四、手写实现MyPromise源码

要求:尽可能还原Promise中的每一个API,并通过注释的方式描述思路和原理
myPromise.js

//定义状态常量
const PENDING = 'pending'; //等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected' //失败


class MyPromise {
    constructor (executor) {
        //调用执行器
        executor(this.resolve, this.reject)
    }
    //定义状态 默认为等待
    status = PENDING;
    // 定义成功之后的值
    value = undefined;
    //定义失败之后的原因
    reason = undefined;
    //成功回调
    successCallback = [];
    //失败回调
    failCallback = []
    resolve = value => {
        // 判断状态是否为等待
        if (this.status !== PENDING) return;
        // 将状态更改为成功
        this.status = FULFILLED
        // 保留成功之后的值
        this.value = value
        // 判断成功回调是否存在, 如果存在 调用
        // this.successCallback && this.successCallback(this.value);
        while (this.successCallback.length) this.successCallback.shift()(this.value)
    }
    reject = reason => {
        // 判断状态是否为等待
        if (this.status !== PENDING) return;
        // 将状态更改为成功
        this.status = REJECTED
        //保留失败的原因
        this.reason = reason
        // 判断失败回调是否存在, 如果存在 调用
        // this.failCallback && this.failCallback(this.reason);
        while (this.failCallback.length) this.failCallback.shift()(this.reason)
    }
    then (successCallback, failCallback) {
        // 当promise 链式调用 then 方法时 返回promise 对象, 那么我们就要创建一个 promise 对象, 并返回
        let Promise2 = new MyPromise((resolve, reject) => {
            // 判断状态
            if (this.status === FULFILLED) {
                let x = successCallback(this.value)
                // 判断 x 的值是普通值还是promise对象
                // 如果是普通值 直接调用resolve 
                // 如果是promise对象 查看promsie对象返回的结果 
                // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
                resolvePromise(x, resolve, reject)
            } else if (this.status === REJECTED) {
                failCallback(this.reason)
            } else {
                // 等待状态
                // 此时须将成功回调与失败回调存储起来
                this.successCallback.push(successCallback);
                this.failCallback.push(failCallback);
            }
        });
        return Promise2;
    }
}

function resolvePromise (x, resolve, reject) {
    if (x instanceof MyPromise) {
        // Promise 对象
        // x.then(value => resolve(value), reason => reject(reason))
        x.then(resolve, reject);
    } else {
        // 普通对象
        resolve(x)
    }

}

module.exports = MyPromise;

// index.js

/**
 * 1.通过创建 Promise 可以知道 Promise 就是一个类,在执行这个类的时候,需要纯第一个执行器进去,并且执行器会立即执行
 * 2.在执行器终须传递两个函数作为参数,resolve 和 reject,并且这两个参数是改变Promise状态
 *   Promise 中有三种状态 成功(fulfilled) / 失败(rejected) / 等待(pending)
 *   pending -> fulfilled
 *   pending -> rejected
 *   一旦状态确定就不可更改
 * 3.resolve 和 reject 函数是用来更改状态的
 *   resolve:fulfilled
 *   reject: rejected
 * 
 * 4. then 方法内部做的事情就是判断状态 如果状态是成功 - 调用成功回调函数; 如果状态是失败 - 就调用失败回调函数
 *   因为每一个 Promise 都可以调用 then 方法,那么这个 then 方法应该是在原型对象上的
 * 5. then 成功回调有一个参数 - 表示成功之后的值;then 失败回调函数有一个参数 - 表示失败之后的原因
 * 6. 同一个 Promise 对象相面的then 方法,可以被多次调用
 * 7. then 方法是可以被链式调用的,后面的 then 方法的回调函数拿到的值是上一个 then 方法的回调函数的返回值
 * 
 * 
 * 
 */
const MyPromise = require('./myPromise')

 let promise =  new MyPromise((resolve, reject) => {
    // setTimeout(()=> {
    //     resolve('成功....')
    // }, 2000)
    resolve('成功')
    // reject('失败')
 })

 function other () {
    return new MyPromise((resolve, reject) => {
        resolve('other')
    })
 }

//  promise.then(value => {
//      console.log(value)
//  }, reason => {
//     console.log(reason);
//  })

//  promise.then(value => {
//     console.log(value)
// }, reason => {
//    console.log(reason);
// }) 
// promise.then(value => {
//     console.log(value)
// }, reason => {
//    console.log(reason);
// })

// promise 链式调用
promise.then(value => {
    console.log(value)
    return other();
}).then(value => {
    console.log(value)
})
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值