一. 谈谈你是如何理解JS异步编程的,EventLoop是做什么的、消息队列是做什么的,什么是宏任务,什么是微任务? JS是个单线程的执行的,通过队列的形式,代码会完成一个任务后再去执行下一个任务即我们常说的'同步模式'。但是有些任务比较耗时,比如定时器,如果我们等待这些任务执行完了,再去执行下一个任务,代码执行就会相当耗时,为了解决这个问题就提出了‘异步模式’。所以代码执行是存在同步和异步两种模式,那么怎么去确保同步和异步代码正常执行,且耗时最短,为了运行不阻塞,就采用了事件循环的模式,即EventLoop。 事件循环(EventLoop)处理方式是把同步任务放到主线程中一次执行,遇到了异步任务,则会把异步任务当道消息队列中等待执行,等主线程任务执行完成之后,执行栈清空,从消息队列中取出一个任务放到执行栈继续执行,执行完成之后继续上一步操作,指导全部执行完成 消息队列是来处理异步任务的,从上文我们可以知道,每当遇到异步任务,都会放入消息队列中,执行完毕后,有消息队列通知主线程排队执行 宏任务可以理解为任务队列的任务,包含外层同步代码script,setTimeout/setInterval,setImmediate(IE/node),requestAnimationFrame,I/O,UI rendering,注册事件。 微任务当主线程任务执行完成之后,立即执行,当前宏任务执行完成之后,立即执行,不会等待整个任务。在执行时能获取任务外的上下文,常见的包含Promise.then catch finally,MutaionObserve,process.nextTick(node)等
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 initPromise(time){
return function(str){
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(str);
},time)
})
}
}
let p = initPromise(10);
Promise.all([p('hello'),p('lagou'),p('I ❤ U')]).then(value=>{
const result = value.reduce((total,curr)=>{return total + curr},'');
console.log(result);
});
二. 基于一下代码完成四个练习
const fp = require("lodash/fp");
const cars = [
{name: 'Ferrari FF',horsepower: 600,dollar_value: 700000,in_stock: true},
{name: 'Spyker C12 Zagato',horsepower: 650,dollar_value: 648000,in_stock: false},
{name: 'Jaguar XXR_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 = fp.flowRight(fp.prop('in_stock'),fp.last);
console.log(isLastInStock(cars));
练习2: 实现为
const isFirstName = fp.flowRight(fp.prop('name'),fp.first);
console.log(isFirstName(cars));
练习3: 实现为
let _average = xs => fp.reduce(fp.add, 0, xs)/xs.length
let averageDollarValue = fp.flowRight(_average,fp.map(fp.prop('dollar_value')));
console.log(averageDollarValue(cars));
练习4: 实现为
let _underscore = fp.replace(/\W+/g,'_');
let sanitizeNames = fp.map(fp.flowRight(_underscore,fp.lowerCase));
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}
//app.js
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 (y)=>{
maybe = maybe.map(x => fp.map(val => fp.add(y,val),x))
}
}
let add = ex1();
add(2);
maybe.map(x => {console.log(x)})
/* 练习2. 实现一个函数ex2 ,能够使用fp.first获取列表的第一个元素 */
let xs = Container.of(['do','ray','me','fa','so','la','ti','do']);
let ex2 = () => {
//你需要实现的函数
let b;
xs.map(x => {b = fp.first(x)})
return b
}
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 = () => {
//你需要实现的函数
/* 获取名字 */
let userInfo = safeProp('name',user);
userInfo.map(name => {
console.log(name); //名字
console.log(fp.first(name)); //首字母
})
}
ex3();
/* 练习4.使用Maybe重写ex4, 不要有if语句*/
// let ex4 = function(n){
// if(n){
// return parseInt(n)
// }
// }
let ex4 = n => {
let num;
let _num = Maybe.of(n);
_num.map(x => {num = parseInt(x)});
return n;
}
console.log(ex4());
四. 手写实现MyPromise源码 要求:尽可能还原Promise中的每一个API,并通过注释的方式描述思路和原理
/*
* 1. Promise对象的then方法会返回一个全新的promise对象
* 2. 后面的promsie是为前一个then方法返回的promise注册回调对象
* 3. 前面then方法的返回值会作为后面then方法回调的参数
* 4. 如果前一个then方法返回的是promise方法,那么后面的then方法会等待它的执行结束
* 5. promise.resolve传递一个promsie对象,得到的是原promise对象
* 6. promise的回调会作为微任务执行,不会作为宏任务重新回到队列排队,同理还有process.nextTick(node)与MutationObserver
*/
/**
* promise then方法里面第二个参数捕获异常,与catch捕获异常的区别
* 1. promise then方法里面的异常捕获,只是对上一个promise异常进行注册的回调函数,所以它只能捕获上一个promise里面的错误,无法捕获then方法里面的错误
* 2. 使用catch是then返回的promise的异常捕获注册的回调函数,符合链式调用,而promise里面的错误,会随链式传递一直到捕获到为止,所以它不仅能捕获到上一个promise里面的错误
* 而且能捕获到then方法里面的错误,建议用第二种
*/
/**
* Promise.all (等待所有任务结束返回) 与 Promise.race (等待第一个任务结束就返回)
*/
// 模拟Promise类
var PENDING = 'pending'; //等待
var FULFILLED = 'fulfilled'; //成功
var REJECTED = 'rejected'; //失败
class myPromise {
constructor(fn) {
fn(this.resolve, this.reject);
}
//promise状态
status = PENDING;
//成功的值
value = undefined;
//失败的值
err = undefined;
//成功回调
successFn = [];
//失败回调
failFn = [];
//成功回调
resolve = value => {
//如果已经执行就不继续往下执行
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// this.successFn && this.successFn(this.value);
while (this.successFn.length) this.successFn.shift()();
}
//失败回调
reject = err => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.err = err;
// this.failFn && this.failFn(err);
while (this.failFn.length) this.failFn.shift()();
}
// then方法实现
then(successFn, failFn) {
//判断方法存不存在,不存在默认一个方法
successFn = successFn ? successFn : val => val;
failFn = failFn ? failFn : err => {throw err};
let promise2 = new myPromise((resolve, reject) => {
if (this.status == FULFILLED) { //执行成功的回调
//判断x的值是普通值还是promise对象
//如果是普通值调用resolve
//如果是promise对象,查看promise对象的结果,再根据结果决定调用resolve还是调用reject
//我们要判断是否等于自身,reject抛出错误;利用setTimeout异步传入当前promise
//利用try/catch判断自身是否出现语法错误,reject当前错误
setTimeout(() => {
try {
let x = successFn(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
} else if (this.status == REJECTED) { //执行失败的回调
setTimeout(() => {
try {
let x = failFn(this.err);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
} else {
this.successFn.push(() => {
setTimeout(() => {
try {
let x = successFn(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
this.failFn.push(() => {
setTimeout(() => {
try {
let x = failFn(this.err);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
}
});
return promise2
}
// promise.all接受一个数组,可以是promise对象,可以是值
// 返回一个promise,且成功回调里面的值是数组,根据传入的值的位置确定,不受异步影响
static all(arr) {
//返回数组
let result = [];
let index = 0;
return new myPromise((resolve, reject) => {
//记录原位置,添加到数组
//出现错误里面reject不再执行
function addData(key, value) {
result[key] = value;
index ++;
if(index === arr.length) resolve(result);
}
for (let i = 0; i < arr.length; i++) {
let curr = arr[i];
if (curr instanceof myPromise) { //promise对象
curr.then(val => addData(i,val),err => reject(err))
}else{ //普通值
addData(i,curr);
}
}
})
}
//finally方法无论成功还是失败都返回
finnally(callback){
return this.then(val =>{
return myPromise.resolve(callback()).then(() => val)
},err =>{
return myPromise.resolve(callback()).then(() => {throw err})
})
}
//catch捕获错误
catch(failFn){
return this.then(undefined,failFn)
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if (x instanceof myPromise) { //属于promise对象
return x.then(resolve, reject);
} else { //属于普通值
resolve(x);
}
}
// var promise = new myPromise((resolve, reject) => {
// resolve(4);
// });
// promise.then(val=>{console.log(val)});
// myPromise.all([1,promise,9]).then(val => {console.log(val)});
// promise.then(value=>{
// console.log(value);
// },err=>{
// console.log(err);
// })
// promise.then(()=>{
// console.log("执行第二次!")
// })
module.exports = myPromise;