JavaScript 中的 Promise 详解以及手写一个 Promise
1 Javascript单线程
Javascript的单线程,与它的用途有关。作为浏览器脚本语言,Javascript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定Javascript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
2 同步和异步
2.1 同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
2.2 异步任务
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
3 异步执行的运行机制
3.1
所有同步任务都在主线程上执行,形成一个执行栈。
3.2
主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3.3
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
3.4
主线程不断重复上面的第三步。
4 promise是什么
4.1
主要用于异步计算。
4.2
可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
4.3
可以再对象之间传递和操作promise,帮助我们处理队列。
5 为什么要用promise
5.1
支持链式调用,可以解决回调地狱问题。
5.2
指定回调函数的方式更加灵活。
6 resolve()函数 / reject()函数
6.1
如果调用resolve()函数和reject()函数时带有参数,那么它们的参数会被传递给回调函数。reject()函数的参数通常是Error对象的实例,表示抛出的错误;resolve()函数的参数除了正常的值以外,还可能是另一个 Promise 实例。
const p1 = new Promise((resolve, reject) => {
setTimeout(reject, 3000, '这是第一个promise');
});
const p2 = new Promise((resolve, reject) => {
setTimeout(p1, 1000);
});
p2.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
注意,此时p1的状态会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
上面代码中,p1是一个Promise,3秒之后变为rejected。p2的状态在1秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,导致触发catch()方法指定的回调函数。
6.2
注意,调用resolve()或reject()并不会终结Promise的参数函数的执行。但是,如果调用 resolve() 或 reject() 后再继续调用 resolve() 或 reject() 是不会执行的。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
reject(3); // 不执行
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
// 2
// 1
6.3
一般来说,调用resolve或reject以后,Promise的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
7 then()方法
7.1
then()方法是定义在原型对象Promise.prototype上的。其作用是为Promise实例添加状态改变时的回调函数。
7.2
then()方法返回一个新的状态为resolve的promise实例,根据前面的promise状态,选择相应的状态响应函数执行。
7.3
状态响应函数可以返回新的promise,也可以返回其他值,也可以不返回值。
7.4
如果当前then()方法返回新的promise,那么下一级then()方法会在新的promise状态改变后执行。
7.5
如果当前then()方法返回的是其他值,那么会立即执行下一级then()方法,且其res值为返回值。
7.6
如果当前then()方法没有返回值,那么会立即执行下一级then()方法,且其res值为undefined。
8 catch()方法
8.1
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
8.2
如果将promise处理函数从
const p = new Promise((resolve, reject) => {
...
}).then(res => {}, err => {})
改为
const p = new Promise((resolve, reject) => {
...
}).then(res => {})
.catch(err => {})
那么如果前面的promise是reject状态,其就会进入catch()方法而非then()方法。catch()方法基本和then()方法一致。
另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
8.3
一般来说,不要在 then() 方法里面定义 reject 状态的回调函数(即 then 的第二个参数),总是使用 catch 方法。理由是第二种写法可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try / catch)。因此,建议总是使用 catch() 方法,而不使用 then() 方法的第二个参数。
9 finally() 方法
finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
9.1
finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。所以 finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
10 Promise.all()方法
10.1
Promise.all()用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise。
10.2
其接收的参数为可迭代对象,可迭代对象的元素可以为promise,也可以为其他值,如果为其他值,就会先调用 Promise.resolve 方法,将该参数转为 Promise 实例,再进一步处理。
10.3
当可迭代对象中所有的子promise都完成,该promise完成,返回值是全部promise的结果和非promise的值的数组。
const p1 = new Promise((resolve, reject) => {
resolve('第一个promise');
});
const p2 = new Promise((resolve, reject) => {
resolve('第二个promise');
});
// promiseAll为一个新的promise
const promiseAll = Promise.all([p1, p2, '这不是promise']);
promiseAll.then(res => {
console.log(res); // ['第一个promise', '第二个promise', '这不是promise']
}).catch(err => {
console.log(err);
})
10.4
有任何一个失败,该promise失败,返回值是数组中第一个失败的promise结果。
const p1 = new Promise((resolve, reject) => {
resolve('第一个promise');
});
const p2 = new Promise((resolve, reject) => {
resolve('第二个promise');
});
const p3 = new Promise((resolve, reject) => {
reject('第三个promise');
});
const p4 = new Promise((resolve, reject) => {
reject('第四个promise');
});
// promiseAll为一个新的promise
const promiseAll = Promise.all([p1, p2, p3, p4, '这不是promise']);
promiseAll.then(res => {
console.log(res);
}).catch(err => {
console.log(err); // '第三个promise'
})
11 promise.race()方法
11.1
Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
11.2
其接收的参数为可迭代对象,可迭代对象的元素可以为promise,也可以为其他值,如果为其他值,就会先调用 Promise.resolve 方法,将该参数转为 Promise 实例,再进一步处理。
11.3
当可迭代对象中所有某一个 promise 完成,该 promise 完成,返回值是第一个完成的 promise 的结果。
12 手写 Promise
class myPromise {
constructor(executor) {
const self = this;
self.value = null;
self.status = 'pending';
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
const resolve = function (value) {
setTimeout(() => {
if (self.status == 'pending') {
self.value = value;
self.status = 'fulfilled';
self.onFulfilledCallbacks.forEach(item => {
item(value);
})
}
});
}
const reject = function (reason) {
setTimeout(() => {
if (self.status == 'pending') {
self.value = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => {
item(reason);
})
}
});
}
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled != 'function') {
onFulfilled = value => value;
}
if (typeof onRejected != 'function') {
onRejected = reason => {
throw reason;
}
}
const promise2 = new myPromise((resolve, reject) => {
if (this.status == 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}
if (this.status == 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}
if (this.status == 'pending') {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
})
this.onRejectedCallbacks.push((value) => {
setTimeout(() => {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
})
}
})
return promise2;
}
}
const resolvePromise = function (promise2, x, resolve, reject) {
if (promise2 == x) {
return reject(new Error('Chaining cycle detected for promise'));
}
let called = false;
if (x != null && (typeof x == 'function' || typeof x == 'object')) {
try {
const then = x.then;
if (typeof then == 'function') {
then.call(x, y => {
if (called) {
return;
}
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) {
return;
}
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (error) {
if (called) {
return;
}
called = true;
reject(error);
}
} else {
resolve(x);
}
}

浙公网安备 33010602011771号