单线程的 javascript
javascript是一门单线程的编程语言,它同一时间只能处理一件事情,这样的概念应该在每一个使用或了解javascript的程序员的脑海中应该都根深蒂固,也是js最基础的知识之一。
在业务越来越复杂,网络连接和页面交互操作越来越密切,为了将单线程的js实现为异步,各种方法层出不迭。但我脑海中的第一个问题是:为什么javascript是单线程的?
答:javascript最初被设计为运行在浏览器中的脚本语言,主要负责页面的各种操作,尤其是dom的操作。试想一下,如果同时有一个操作添加了dom,但同时又有一个操作删除了dom,是不是就会带来紊乱?
单线程模式的一个缺点是,如果有三个任务ABC,A一直没有完成,堵塞了线程,BC任务就陷入了永远的等待...所以为了解决这个问题,js虽然是单线程的,但它也有异步,js的执行机制是event-loop(事件轮循)
event-loop机制
同步:执行顺序和任务的排列顺序一致
异步:不确定是不是立刻执行,可能要满足一定条件后才会执行
js的执行顺序:
- 在执行一段代码时,首先判断它是同步还是异步,是可以立即执行,还是需要满足一定条件才能执行?如果是同步代码,则放入执行栈;异步代码则开始执行后,不等待结果,先挂起事件。
- 当异步执行操作返回结果后,这个异步操作的结果会被加入消息队列(callback queue),等当前执行栈中的同步代码运行完后,按照顺序从callback queue中取任务(异步的回调)来运行。
- 以上的过程可以进行无限的循环,这个过程就是event-loop事件轮询机制
回调函数
function say(value) {
console.log('say:', value);
}
function exec(func, param) {
func(param);
}
exec(say, 'today is Friday');
将一个函数A作为参数传递给另一个函数B,并在函数B内部调用函数A,就称为回调函数。这也是起初能解决js异步的方法之一,但随着要等待的结果越来越多,要处理的状态越来越复杂,回调可能会出现:A1->A2->A3->A4这样子需要按照顺序执行的情况,每一个回调函数都作为参数传给上一个,造成了回调地狱的问题。
micro-task && macro-task
怎么会区分宏任务和微任务? 宏任务是由宿主(node、浏览器)发起的,而微任务是由JS引擎自行发起的 个人没有怎么使用过I/O和process()等,所以主要讨论常用的。
- 宏任务:setTimeout,setInterval
- 微任务:Promise
执行顺序:主线程任务->遇到异步挂起->异步进入任务队列(区分宏任务和微任务)->微任务执行直到为空->执行宏任务......
现实类比:在银行办事时,宏任务是每个要办事的人,微任务是每个人到了柜台后要处理完的所有个人事宜。
Promise
通过手写promise的实现,来了解Promise:
/**
* promise接受一个函数作为参数,函数有reject和resolve两个参数
* resolve负责将执行结果作为参数传出,且将promise的状态变更为fulfilled
* reject负责将如果执行出现了错误,就将错误传出,且将状态变更为rejected
*/
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
/**
* x是then的返回结果
* newPromise是默认返回的promise
* resolve是newPromise的resolve,也就是默认promise的resolve,需要根据x决定是什么内容
* reject是newPromise的reject,也就是默认promise的reject,需要根据x决定是什么内容
* 1. x不能和newPromise相等,因为newPromise等待x,如果x === newPromise,会循环
* 2. x不能为null
* 3. x如果是普通类型的值,直接将这个值透传,作为newPromise的resolve要传递出去的参数
* 4. x如果是函数或者对象,看能不能let then = x.then;
* 4.1 then报错,则走reject()
* 4.2 可以成功的then(),则视x为thenable,执行then(),第一个参数是this,然后是成功和失败的回调
* 4.3 如果成功的回调还是promise,继续递归
* 5. 不管成功还是失败,都只调用一次
*/
function resolvePromise (x, newPromise, resolve, reject) {
if (x === newPromise) {
// 使用reject抛出错误
return reject(new TypeError('endless loop with same x and newPromise'));
}
// 因为上方return了,不用else,声明called防止重复调用resolve或reject
let called;
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 看看thenable,取then的时候注意防止报错
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (newValue) => {
// resolve的结果如果还是promise,继续解析
if (called) return;
called = true;
resolvePromise(newValue, newPromise, resolve, reject);
}, (newReason) => {
// reject
if (called) return;
called = true;
reject(newReason);
})
} else {
// 不是thenable,当成基本类型透传
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 直接透传
resolve(x);
}
}
class MyPromise {
/**
* Promise 1: 执行传入的函数,使用resolve和reject作为参数,改变promise的状态
*/
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulFilledCallback = [];
this.onRejectedCallback = [];
/**
* executor需要两个参数: resolve和reject
*/
let resolve = (value) => {
this.value = value;
this.state = FULFILLED;
// 开始将onFulFilledCallback里的方法都取出来执行
this.onFulFilledCallback.forEach(fn => fn());
}
let reject = (reason) => {
this.reason = reason;
this.state = REJECTED;
this.onRejectedCallback.forEach(fn => fn());
}
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
/**
* Promise 2:
* promise有一个叫then的方法,传入onFulFilled和onRejected两个参数
* 成功则执行onFulFilled,传入value
* 报错则执行onRejected,传入reason
*/
then(_onFulFilled, _onRejected) {
/**
* Promise 3:
* 为了实现链式调用,不再出现第二个then的目标对象为undefined的情况,then要返回一个newPromise
* 但then自己会返回一个x(onFulFilled或onRejected返回的值),x可能是任何值
* 判断x和newPromise的方法是resolvePromise,resolvePromise有自己的规则
*/
let onFulFilled = typeof _onFulFilled === 'function' ? _onFulFilled : value => value;
let onRejected = typeof _onRejected === 'function' ? _onRejected : err => { throw err };
let newPromise = new MyPromise((resolve, reject) => {
// 将重复的方法抽出来
let autoResolve = () => {
let x = onFulFilled(this.value);
resolvePromise(x, newPromise, resolve, reject);
};
let autoReject = () => {
let x = onRejected(this.reason);
resolvePromise(x, newPromise, resolve, reject);
};
/**
* Promise 4:
* onFulfilled如果不是函数,直接value => value
* onRejected如果不是函数,直接reason
* onFulfilled和onRejected必须被异步调用,因为需要保持代码的一致性:如果它们不是异步的,在某些场合下同步,某些场合下异步,无法保证执行顺序,如果是异步的,根据event-loop,它们必将在同步代码都执行后执行
*/
if (this.state === FULFILLED) {
// x是onFulFilled的结果
// let x = onFulFilled(this.value);
// resolvePromise(x, newPromise, resolve, reject);
setTimeout(() => {
try {
console.log('autoResolve');
autoResolve();
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === REJECTED) {
console.log(this.state);
// x是onRejected的结果
// let x = onRejected(this.reason);
// resolvePromise(x, newPromise, resolve, reject);
setTimeout(() => {
try {
autoReject();
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === PENDING) {
/**
* 将onFulFilled和onRejected放入callback中
* 为了链式调用,push进去的待执行的,也需要有x和resolvePromise的处理
*/
this.onFulFilledCallback.push(() => {
setTimeout(() => {
try {
autoResolve();
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallback.push(() => {
setTimeout(() => {
try {
autoReject();
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return newPromise;
}
}
// promises-aplus-tests的测试脚本
MyPromise.deferred = function() {
const defer = {}
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
try {
module.exports = MyPromise;
} catch (e) {}
通过测试npx promises-aplus-tests promise.js的872个测试用例
还有常用的Promise.all(),Promise.race()两种方法,一个是等待Promise内所有任务的完成,一个是竞争完成。
async、await、yield
根据es6的定义,async和await其实是Generator函数的语法糖,假如我们想用async和await实现下面的Generator函数:
思考
- 有一个页面,从上到下分为ABC三个区域,都需要通过接口请求返回数据显示。三个请求同时发出,需要达成如下场景:A接口成功,显示A;B接口成功,显示B;C接口成功,显示C。在A显示前,BC不显示;在AB显示前,C不显示。