简答题
一、js异步编程实际是将部分js代码的执行滞后。js是单线程的,如果没有异步,对于某些耗时较大的操作,如异步获取数据,IO操作等,js将等待操作完成再执行下面的代码,这样会造成界面卡顿、白屏等现象。而js的异步编程就是用来解决这个问题的。
js在执行时,会将js代码解析成一个一个任务,然后依次执行。任务分为同步任务和异步任务。
- 同步任务
js在执行同步任务时会将任务添加到执行栈中并立即执行,等待任务执行完成之后再将其从执行栈中弹出。 - 异步任务
js在执行异步任务时,会经过相应的处理模块处理之后添加到任务队列中(也称消息队列)。在执行栈为空时,会从任务队列头部取出一个任务放到执行栈中执行,执行完成之后将其从执行栈中弹出。执行栈为空之后,会再次从任务队列里头部取出一个任务放到执行栈中执行,这个过程称为时间循环(即EventLoop)。
异步任务又分为宏任务和微任务
- 宏任务:需要进入到任务队列中重新排队的任务,如setTimeout、setInterval等
- 微任务:不需要重新进入到任务队列中重新排队的任务,如Promise、MutationObserver以及node中的process.nextTick。
执行栈为空之后,首先会从微任务队列中取出任务执行,待微任务队列为空之后再从宏任务队列中取出任务执行。
代码题
一、
1、使用Promise改进setTimeout
const p = val => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(val)
}, 10)
});
p('hello').then(val => {
return p('lagou').then(value => val + value)
}).then(val => {
return p('I ♥ U').then(value => val + value)
}).then(console.log);
二、
1、使用fp.flowRigth重新实现isLastInStock
const isLastInStock = fp.flowRight(fp.props('in_stock'), fp.last);
2、使用fp.flowRight、fp.props、fp.first获取第一个car的name
const isFirstInName = fp.flowRight(fp.props('name'), fp.first);
3、使用函数组合实现averageDollarValue
const averageDollarValue1 = fp.flowRight(_average, fp.map(car => {
return car.dollar_value;
}));
4、使用fp.flowRight实现一个能让数组中name的转换为下划线连接的小写字母字符串
const sanitizeNames = fp.map(fp.flowRight(_underscore, fp.lowerCase, car => car.name));
三、
1、使用fp.add和fp.map创建一个能让functor里面的值增加的函数
const ex1 = (y) => {
return maybe.map(fp.map(fp.add(y)));
}
2、使用fp.first获取列表的首字母
const ex2 = () => {
return xs.map(fp.first)
}
3、使用safeProp和fp.first找到user的name的首字母
const ex3 = () => {
return safeProp('name')(user).map(fp.first)
}
4、重写ex4,不要有if
const ex4 = (n) => {
return Maybe.of(n).map(parseInt)
}
四、手动实现Promise
/**
*
* promise功能实现:
* 1、Promise是一个对象,接收一个处理器函数并立即执行
* 2、处理器函数接收两个参数,resolve和reject。resolve将状态改为成功,reject将状态改为失败。
* 3、Promise有三个状态,pending:初始等待状态,fulfilled:成功,rejected:失败,
* 并且只能由pending -> fulfilled 或 由 pending -> rejected。
* 状态一旦改变就不能再次改变。
* 4、原型方法then接收两个参数,更具状态判断执行哪个参数。
* fulfilled 执行第一个成功的回调函数,并将值作为参数传递,值是调用executor中resolve传入的。
* rejected 执行第二个失败的回调函数,并将失败原因作为参数传递,失败的原因是executor中调用reject传入的。
* then的回调函数返回值的一些情况:
* 1、返回一个值,则then返回一个成功状态的Promise,并将返回值作为成功回调的参数
* 2、没有返回值,则then返回一个成功状态的Promise,并将undefined作为成功回调的参数
* 3、抛出一个错误,则then返回一个失败状态的Promise,并将错误对象作为失败回调的参数
* 4、返回一个成功状态的Promise,则then也会返回一个成功状态的Promise,并将Promise执行函数中的resolve的参数作为then的成功回调函数的参数
* 5、返回一个失败状态的Promise,则then也会返回一个失败状态的Promise,并将Promise执行函数中的reject的参数作为then的失败回调函数的参数
* 6、返回一个等待状态的Promise,则then也会返回一个等待状态的Promise,并且在改变状态之后将使用Promise执行函数中的resolve/reject的参数作为对应回调函数的参数
*/
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// 执行器错误捕获处理
try {
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e);
}
}
// promise 的状态
status = PENDING;
// 保存值
value = undefined;
// 保存失败的原因
reason = undefined;
// 成功回调函数
successCallBacks = [];
// 失败回调函数
failCallBacks = [];
resolve = (value) => {
// 判断如果状态不是 pending,直接返回,状态一旦改变,并能再次改变
if(this.status !== PENDING) return;
// 保存 promise 的状态为 成功
this.status = FULFILLED;
// 保存值
this.value = value;
// 如果保存有异步回调,从第一个开始一次执行
while(this.successCallBacks.length) {
this.successCallBacks.shift()()
}
};
reject = (reason) => {
// 判断如果状态不是 pending,直接返回,状态一旦改变,并能再次改变
if(this.status !== PENDING) return;
// 保存 promise 的状态为 失败
this.status = REJECTED;
// 保存失败的原因
this.reason = reason;
// 如果保存有异步回调,从第一个开始一次执行
while(this.failCallBacks.length) {
this.failCallBacks.shift()()
}
};
then(successCallback, failCallback) {
// 参数处理
successCallback = successCallback ? successCallback : value => value;
failCallback = failCallback ? failCallback : reason => { throw reason; };
// 返回一个Promise
let resultPromise = new MyPromise((resolve, reject) => {
if(this.status === FULFILLED) { // 成功
// 将同步代码变成异步代码,保证resultPromise能取到正确的值
// 这里不能将setTimeout放到方法中,因为方法执行时resultPromise还没有取到最新值
setTimeout(() => {
thenCodeExecute(successCallback, this.value, resultPromise, resolve, reject)
}, 0)
} else if(this.status === REJECTED) {// 失败
// 将同步代码变成异步代码,保证resultPromise能取到正确的值
setTimeout(() => {
thenCodeExecute(failCallback, this.reason, resultPromise, resolve, reject)
}, 0)
} else { // 等待状态,存在异步回调
// 保存异步回调函数,可能存在多个then调用
this.successCallBacks.push(() => {
setTimeout(() => {
thenCodeExecute(successCallback, this.value, resultPromise, resolve, reject)
}, 0)
});
this.failCallBacks.push(() => {
// 将同步代码变成异步代码,保证resultPromise能取到正确的值
setTimeout(() => {
thenCodeExecute(failCallback, this.reason, resultPromise, resolve, reject)
}, 0)
})
}
});
return resultPromise;
}
/**
* 接收一个回调函数,返回一个promise对象,promise对象和前一个then返回的promise的状态和值都一致
* 回调函数不论成功还是失败状态都会执行,如果回调函数返回一个promise,则会等待promise执行完成再执行后面的调用
* @param callback
* @returns {*}
*/
finally(callback) {
// 调用then方法确定promise的状态,并进行对应的处理
return this.then(value => {
// 将回调函数的结果转化成Promise对象,再通过Promise的then方法将value/reason向下传递
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
return MyPromise.resolve(callback()).then(() => {throw reason;});
})
}
/**
* 返回一个失败状态的promise,并将callback的结果向下传递
* @param callback
* @returns {*}
*/
catch(callback) {
return this.then(undefined, callback)
}
/**
* 合并执行Promise,当所有传入的promise都成功时,将返回成功状态的Promise对象,并将所有Promise的结果传入resolve中
* 当有一个Promise的状态为失败时,返回一个失败状态的Promise对象
* @param array
* @returns {MyPromise}
*/
static all(array) {
const r = [];
let index = 0;
const len = array.length;
return new MyPromise((resolve, reject) => {
function addElement(key, data) {
r[key] = data;
index++;
// 判断结果集中元素个数是否和参数中数组的元素数量一致,如果一致则表示所有Promise都执行完毕
// 因为普通元素会先一步存入到结果集中(因为是同步的),而Promise是异步的,所以for循环完成之后才开始将成功的结果放入到结果集中。
if(index === len) {
resolve(r);
}
}
for (let i = 0; i < len; i++) {
const current = array[i];
if (current instanceof MyPromise) { // Promise对象,成功时将返回值存入到结果集中,失败时将返回的Promise的状态变成失败,并将原因传入
// 闭包保存 i 的值
current.then(val => addElement(i, val), reason => reject(reason))
} else { // 普通值,直接将当前数组元素存入到结果的对应位置
addElement(i, current)
}
}
})
}
/**
* 将传入的参数转化为一个Promise对象,如果参数本身是一个Promise对象,将原样返回
* 如果参数不是一个Promise对象,将作为成功状态的返回值向下传递
* @param value
* @returns {MyPromise}
*/
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value))
}
// 返回一个带有拒绝原因的Promise对象
static reject(value) {
return new MyPromise((resolve, reject) => reject(value))
}
/**
* 接收一个数组,返回一个Promise,
* 只要有一个Promise返回成功或失败状态,就确定了Promise的状态
* 返回的Promise状态及参数和最先确定状态的Promise一致
* 如果数组中的元素为非Promise对象,则立即返回一个成功状态Promise,resolve参数为该数组元素
* @param array
* @returns {MyPromise}
*/
static race(array) {
return new MyPromise((resolve, reject) => {
for(let i = 0, len = array.length; i < len; i++) {
const current = array[i];
if (current instanceof MyPromise) {
current.then(resolve, reject);
} else {
resolve(current);
}
}
})
}
}
/**
* 获取then方法回调函数的返回结果,并根据结果确定then方法返回的Promise的状态及对应参数
* @param callback 回调函数
* @param value 回调函数执行时的参数
* @param resultPromise 要返回的promise对象
* @param resolve resultPromise执行器的resolve方法
* @param reject resultPromise执行器的reject方法
*/
function thenCodeExecute(callback, value, resultPromise, resolve, reject) {
try {
resolvePromise(resultPromise, callback(value), resolve, reject);
} catch (e) {
reject(e);
}
}
/**
* 根据then方法回调函数的返回值,确定then方法返回的promise的状态及参数。
* @param resultPromise then方法返回的promise对象
* @param callbackResult then方法回调函数的返回值
* @param resolve resultPromise执行器的resolve方法
* @param reject resultPromise执行器的reject方法
* @returns {*}
*/
function resolvePromise(resultPromise, callbackResult, resolve, reject) {
// then的返回值返回了自身,将Promise状态变为reject,并传递错误原因。
if (resultPromise === callbackResult) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (callbackResult instanceof MyPromise) {
/**
* 返回值是Promise对象,根据返回值Promise对象的状态将对应值传递给对应的回调函数
* 1、返回值Promise状态是成功状态,将成功状态的返回值传递给resolve
* 2、返回值Promise状态是失败状态,将失败状态的返回值传递给reject
*/
callbackResult.then(resolve, reject)
} else { // 返回值是普通值, 直接调用
resolve(callbackResult)
}
}
module.exports = MyPromise;