一 、浏览器执行异步代码的过程
一段js脚本通常由多个块组成,最常见的块单位是函数。对于js脚本,浏览器会调用js引擎进行解析和执行。如果在解析过程中遇到异步代码,js引擎会通知浏览器,等这段异步代码执行完成时,调用回调。浏览器就会监听异步事件,一旦获取到结果,就会把回调放入事件循环中,等待执行。
所以,js引擎不是独立运行的,也不是按照代码的书写顺序一行一行执行的,而是听从宿主环境(大多数情况下是浏览器)的调度,按需执行。
二、回调简介
最常见的处理异步结果的方式就是回调,回调的方式使js引擎不必等待异步代码的执行,从而减少代码阻塞,带来更好的交互体验。但回调并不符合我们的思考顺序,链式回调也会使代码难以理解,并可能会带来隐藏的bug
- 对于异步代码回调的执行,依赖于浏览器调用js引擎的时间,而不是代码顺序,比如下面这个例子
var x,y,z;
function f1(data) {
x = data;
}
function f2(data) {
y = data;
}
function f3() {
return x + y;
}
var x = axios.get('xx.com', f1)
var y = axios.get('bb.com', f2)
z = x + y
// 理想的结果是,执行到z = x + y时,已经获取到了x和y的值,但由于回调,此时的值还是不确定的
- 回调会导致代码不易理解
这里的不易理解,不在于回调嵌套层级,而在于异步和同步代码可能同时存在
doA(function(){
doC();
doD(function(){
doF()
})
doE()
})
doB()
// 这里的执行顺序时A B C D E F,但是如果doA()或者doD()不是异步,那结果就是A C D F E B
- 回调引发的信任问题
我们使用的回调大多数是第三方提供的,对于回调什么时候调用,调用多少次,对我们来说是不确定的,并且,大多数的回调没有内置的错误处理机制,需要我们手动添加和处理
三 、promise简介
promise作为js内置的一种异步处理方案,比较好的解决了回调带来的问题
- 代码的执行顺序
对于promie来说,代码的执行具有归一性,顺序性
new Promise((resolve, reject) => {
////
console.log('begin');
resolve(x)
}).then((x) => {
console.log(x) // 当我们使用x时,x的值已经是确定的,对比回调的第一个例子,在z = x + y中,x和y的值还不确定
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('then1');
resolve()
})
})
}).then(() => {
console.log('then2')
})
只要在then中返回一个promise或者一个值,那么then一定是按顺序调用,并且是异步调用的
- 信任问题
对于同一个promise,即时调用resolve或reject多次,它上面的then只会执行一次,并且一定是异步执行。promise还提供了错误处理机制catch
虽然promise解决了回调带来的问题,但它本身也有一定的局限性
- promise无法终止,并且如果catch中抛错,promise也无法捕获到
function textError() {
try {
new Promise((resolve, reject) => reject(1));
p1.catch(e => foo()) // foo不存在,会抛错,但不会被catch捕获到,因为try catch只能捕获同步错误
}catch(e) {
console.log('a')
}
console.log('dfs')
}
- promise的单决议,即promise的值只会获取一次
var p = new Promise((resolve, reject) => {
window.addEventListener('resize', resolve)
})
p.then(() => alert('resize'))
// 只有第一次resize的时候,会弹出alert
五 、实现promise
可以把promise看作事件监听,当处于完成状态,调用then方法。当失败时,会调用catch方法,针对以上对promise的理解,我们可以来写写promise(为了简化,只写了成功状态)
- then函数,接受一个函数,根据函数的返回结果,来判断当前promise的状态
- resolve函数,接受一个值,如果是普通值,直接调用注册的then函数,如果值是promise,会等待这个promise的状态,根据promise的状态判断调用then还是catch,比如下面这个例子
new Promise((resolve, reject) => {
resolve(Promise.reject(1))
}).then(res => console.log('res', res)).catch(e => console.log('ee'))
在这里调用的是catch,而不是then
简单promise实现
function MyPromise (resolver) {
if (typeof resolver !== 'function') {
throw new TypeError('Promise resolver ' + resolver + ' is not a function')
}
if (!(this instanceof MyPromise)) return new MyPromise(resolver);
var self = this;
self.status = 'pending';
self.onFulfilledCallbacks = [];
const resolve = (res) => {
var resStatus = Promise.resolve(res) //
if(resStatus === 'fulfilled') {
setTimeout(() => {
self.status = 'fulfilled';
self.data = res;
this.onFulfilledCallbacks.map(callback => callback.onResolved(res));
});
}
}
resolver(resolve);
}
MyPromise.prototype.then = function(onFulfilled) {
var self = this;
var promise2;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v
// 决定新返回的promise状态
const resolvePromise = (promise, x, resolve, reject) => {
var thenCalledOrThrow = false;
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise!'))
} else if ((x !== null) && (typeof x === 'object' || typeof x === 'function')) {
try {
if (typeof x.then === 'function') {
try {
x.then.call(x, function rs(y) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return resolvePromise(promise, y, resolve, reject);
});
} catch(e) {
Promise.reject(e);
}
} else {
resolve(x);
}
} catch(e) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
reject(e);
}
} else {
return resolve(x);
}
}
if (self.status === 'pending') {
return promise2 = new MyPromise((resolve, reject) => {
self.onFulfilledCallbacks.push({
onResolved: function (val) {
try {
const x = onFulfilled(val);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}
});
});
}
// 要保证then始终是异步调用的,这样代码的执行顺序才不会乱,因为new Promise()中可能有同步代码
if (self.status === 'fulfilled') {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const x = onFulfilled(val);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
var promise1 = new MyPromise((resolve) => setTimeout(() => resolve('success'))).then((res) => console.log('res-----', res));
console.log('promise1', promise1);
setTimeout(() => {
console.log('promise1----', promise1);
}, 1000);