Promise梳理
什么是Promise?
Promise是异步问题同步化的解决方案,不会阻塞同步代码的执行。通过resolve和reject改变Promise当前的状态,then获取异步请求之后的结果,从而避免回调地狱。
- 语法:Promise是ES6提供的构造函数,通过new创建实例
- 状态:pending(进行中),fufilled(已成功),rejected(已失败)
- 参数:executor执行函数,自动执行
- resolve 将状态由 pending 改为 fulfillled,触发then中成功的回调
- reject 将状态由 pending 改为 rejected,触发then中失败的回调
- then(onFulfilled 成功的函数, onRejected 失败的函数)
- 如果不是函数,会被忽略
- resolve 里的 value 作为 onFulfilled 函数的参数, reject同理
- 必须返回一个promise,用于链式调用,上一次then的return值作为下一次then的执行参数
let promise = new Promise((resolve, reject) => { // executor
// resolve('success');
// reject('error');
throw new Error('Exception: Error'); // 抛出错误走reject
});
promise.then((val) => {
console.log(val, '成功的回调');
}, (reason) => {
console.log(reason, '失败的回调');
})
要注意的是状态不受外界影响,一旦发生改变就会发生固化,不可逆
let promise = new Promise( (resolve, reject) => {
resolve('ok');
console.log(a);
})
promise
.then(function(val) {
console.log('success: ' + val); //success: ok
})
.catch(function(error) {
console.log('reject: ' + error);
})
状态固化之后,哪里都不会捕获异常。console.log(a)依然会执行,但是无法捕获
resolve和reject并不会终止函数运行
为什么要使用Promise?
要知道js是单线程的(多线程会造成DOM冲突),单线程阻塞页面的解决方案就是异步,异步是通过事件轮询的机制实现的,而事件轮询的核心就是回调函数。然而如果回调函数层层嵌套,造成回调地狱,就会导致代码难以维护和扩展。
const fs = require('fs');
fs.readFile('./name.txt', 'utf-8', (err, data) => {
if (data) {
fs.readFile(data, 'utf-8', (err, data) => {
if (data) {
fs.readFile(data, 'utf-8', (err, data) => {
console.log(data);
}
}
}
}
})
使用Promise就可以使代码清晰很多
function readFile(path) {
return new Promise( (resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (data) {
resolve(data);
}
}
})
}
readFile('./name.txt')
.then( data => readFile(data))
.then( data => readFile(data))
.then( data => console.log(data));
promisify
promisify方法可以将异步函数promise化,任何异步函数都适用
const fs = require('fs');
function promisify(func) {
return function(...args) {
return new Promise( (resolve, reject) => {
func(...args, (err, data) => {
if (data) {
resolve(data);
} else {
reject(err);
}
}
})
}
}
let readFile = promisify(fs.readFile);
readFile('./name.txt', 'utf-8')
.then(data => readFile(data, 'utf-8'))
.then(data => readFile(data, 'utf-8'))
.then(data => console.log(data));
事件轮询 - 宏任务与微任务
事件轮询的机制:执行JS代码的时候,遇到同步任务,直接压入主线程进行执行;遇到异步任务,让任务挂起,等到异步任务有返回值之后,推入任务队列中。等到主线程所有的同步代码执行完毕,将任务队列中的任务逐个推入主线程并执行,重复执行这一系列行为。
异步任务包含宏任务与微任务,微任务优先级高于宏任务
宏任务:宿主(浏览器/Node)提供的异步方法与任务(script,XHR回调,setTimeout,UI渲染,I/O,setImmediate)
微任务:语言标准(ECMA262)提供的API运行(Promise,Mutation Observer,process.nextTick)
setTimeout(function() {
console.log('setTime')
}, 30);
let promise = new Promise(function(resolve, reject) {
console.log(0);
resolve(1);
});
promise.then( (val) => {
console.log(val);
}, (reason) => {
console.log(reason);
})
console.log(2); //0 2 1 setTime
过程解析:
- 看同步代码,Promise内部的函数执行是同步的(resolve才是promise的异步操作),所以0先打印,然后是2
- 微任务优先,所以先处理promise的回调,打印1
- 宏任务 setTimeout
Promise.all
参数:具有iterator接口的数据结构,如数组
保证在参数里的所有promise都成功的状态下,返回新的promise对象,并且把包含所有返回值的数组,作为成功回调的返回值
catch只会抓住第一个错误
let promise1 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve('promise1: 1s');
}, 1000);
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve('promise2: 2s');
}, 2000);
})
let promise3 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve('promise3: 3s');
}, 3000);
})
const p = Promise.all([promise1, promise2, promise3]);
p.then(res => console.log(res))
.catch(err => console.log('err: ' + err));
// 返回值:["promise1: 1s", "promise2: 2s", "promise3: 3s"]
手写Promise.all
Promise.all = function (iterator) {
let count = 0 //用于计数,当等于len时就resolve
let len = iterator.length
let res = [] //用于存放结果
return new Promise((resolve, reject) => {
for(let i in iterator){
Promise.resolve(iterator[i])//先转化为Promise对象
.then((data) => {
res[i] = data;
if(++count === len){
resolve(res)
}
})
.catch(e => {
reject(e)
})
}
})
}
Promise.race
用法和all差不多,不一样的是,不管成功还是失败都只会返回第一个promise
Promise.race = function (iterators) {
return new Promise((resolve,reject) => {
for (const p of iterators) {
Promise.resolve(p)
.then((res) => {
resolve(res)
})
.catch(e => {
reject(e)
})
}
})
}
Promise.resolve
Promise.resolve / Promise.reject用法相同,都会返回一个新的Promise实例
可以使用resolve方法将普通对象转成promise对象,通过resolve方法部署值
// thenable:所有可以then的对象都是thenable对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
}
let p1 = Promise.resolve(thenable);
p1.then(val => console.log(val));
转空值或者字符串也可以
Promise.resolve('1').then((val) => console.log(val));
Promise.resolve().then(function(){
console.log(2);
})
Promise重写
const PENDING = 'PENDING',
FULFILLED = 'FULFILLED',
REJECT = 'REJECT';
class MyPromise {
constructor (executor) {
this.status = PENDING;
// 在pending阶段resolve和reject不明确
this.value = undefined;
this.reason = undefined;
// 数组装载所有成功 / 失败的回调函数
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 每个promise的resolve/reject不同,不能放到原型上
const resolve = (value) => {
// 递归处理,直到value是个普通值
if (value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 等异步程序执行的时候,resolve执行,可以发布了
this.onFulfilledCallbacks.forEach(fn => fn());
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECT;
this.reason = reason;
// 发布
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject); // executor立即执行
} catch (e) {
reject(e)
}
}
// then会返回一个promise,但是then的return值会有很多种情况,如then直接返回一个promise,就不适用,所以需要一个x对返回值进行额外处理
then (onFulfilled, onRejected) {
// then 传空就把value传递下去即可,穿透
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value => value);
onRejected = typeof onRejected === 'function' ? onRejected : (reason => throw reason);
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
// 不设置异步的话,resolvePromise拿不到promise2,需要promise2的new Promise先执行完,才可以拿到
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECT) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
}
// 异步处理,在pending的时候无法判断是成功的还是失败的回调函数,需要收集(订阅)
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
// 这里不需要异步处理,因为pending状态本身就有异步处理了,resolve的时候才会执行
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
}
});
return promise2;
}
// 用then模拟catch,catch实际上就是then的一个语法糖
catch (errorCallback) {
return this.then(null, errorCallback);
}
static resolve (value) {
return new MyPromise((resolve, reject) => {
resolve(value);
})
}
static reject (error) {
return new MyPromise((resolve, reject) => {
reject(error);
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('promise名称不能和返回值相同'));
}
// 当then里面既有resolve,也有reject的时候,采用先执行的那个,需要一个flag判断是否调用过,避免重复调用resolve/reject
let called = false;
if ((typeof x === 'object' && x !== null) || (typeof x === 'function')) { // 可能是一个promise,需要再判断
try {
// 如果有then方法,基本上就是promise了
let then = x.then; // 取then的时候可能有 throw error,有可能被defineProperty截止then(get),需要try catch
if (typeof then === 'function') {
// promise,改变this -> return的新Promise实例,执行then
then.call(x, (y) => {
if (called) return;
called = true;
// resolve(y); promise递归处理
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
})
} else {
// 普通值
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 普通值
resolve(x);
}
}
总结:
Promise是异步微任务,是异步问题同步化的解决方案,解决了异步多层回调嵌套的问题,使代码的可读性更高,更易维护
Promise是ES6提供的一个构造函数,可以new一个实例出来,接受一个执行函数作为参数,会立即执行。
Promise有三种状态:Pending进行中,Fulfilled已成功,rejected已失败,一旦状态改变了就不会变化了(状态固化),并且状态不受外界影响。
Promise的执行函数有两个参数,resolve将Promise的状态由进行中变为成功,传递异步操作的结果,触发then内部成功的回调;reject将Promise状态变为失败,触发失败的回调。也可以通过catch捕捉失败,then和catch返回的也是一个Promise,用于链式调用。
转载自:https://juejin.cn/post/7248183510234185788