promise
什么是 promise
Promise 是 ES6 的新特性,是异步编程的一种解决方案,从语法上说,Promise 是一个对象,从它可以获取异步操作的消息,可以解决回调地狱(回调地狱嵌套回调函数);
Promise 的含义:
本身不是异步,是封装异步操作容器,统一异步的标准;
Promise 对象的特点:
对象的状态不受外界影响,一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。
传统的异步操作
<script src="node_modules/jquery/dist/jquery.js"></script>
<script>
$.ajax({
url: 'xxx',
success: function(res) {
console.log(res)
$.ajax({
url: 'xxx' + res.id,
success: function(res2) {
console.log(res2)
}
})
}
})
</script>
如果下一个异步依赖上一个异步,需要在上一个异步的回调中去处理下一次的异步,这样的代码会造成嵌套(回调地狱)的问题,会难以阅读和维护。
利用 Promise 处理异步
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。
const promiseGetBooks = new Promise(function (resolve, reject) {
// 回调函数内部进行异步处理
$.ajax({
url: 'xxx',
success: function (res) {
resolve(res);
},
error: function (err) {
reject(err);
},
});
});
promiseGetBooks
.then((res) => {
console.log(res);
return new Promise(function (resolve, reject) {
// 回调函数内部进行异步处理
$.ajax({
url: 'xxx',
success: function (res) {
resolve(res);
},
});
});
})
.then((res) => {
console.log(res);
return new Promise(function (resolve, reject) {
// 回调函数内部进行异步处理
$.ajax({
url: 'xxx',
success: function (res) {
resolve(res);
},
});
});
})
.then((res) => {
console.log(res);
});
Promise 的优缺点及状态
优点
- 将异步操作以同步操作的方式表达出来,避免层层嵌套回调函数
- 提供统一的操作接口,方便对异步操作的控制
缺点
- promise 一旦建立,则不可取消
- 如果不设置回调函数,则会在 promise 内部抛出错误,不会反应到外部
- 当状态是 pending 是,无法判断当前状态(是异步刚刚开始执行还是即将完成了异步操作)
状态
- Pending 等待态:可以变更为执行态或拒绝态
- Fulfilled 执行态:不能再去改变状态,必须包含一个最终值(value)
- Rejected 不能再去改变状态,必须包含一个拒绝的原因(reason)
静态方法-all
Promise.all()参数可以传递一个数组,数组中记录所有的 promise 异步处理
返回值是一个 Promise 的实例对象 then 方法可以获取到所有的 promise 异步处理的结果,一旦有某一个调用执行了 reject,则终止进入 catch
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 0);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 0);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 0);
});
Promise.all([p1, p2, p3]).then((res) => {
console.log(res); //[1,2,3]
});
静态方法-allSettled
Promise.all 和 allSettled 基本一样,区别是,then 始终可以获取到异步的状态,哪怕其中有一个失败
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 0);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 0);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 0);
});
Promise.allSettled([p1, p2, p3]).then((res) => {
console.log(res);
//[{status: 'fulfilled', value: 1},{status: 'rejected', reason: 2},{status: 'fulfilled', value: 3}]
});
静态方法-race
Promise.race 使用和 all 一样,但是只返回第一个结果,不管成功或失败
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 500);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 200);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 300);
});
Promise.race([p1, p2, p3])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err); // 2
});
静态方法-any
Promise.any 返回第一个成功的结果
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 500);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 200);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 300);
});
Promise.race([p1, p2, p3]).then((res) => {
console.log(res); // 3
});
promise A+规范
Promise 源码解析
/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
// 定义三个状态
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
function resolvePromise(x, promise2, resolve, reject) {
//判断x === promise, 抛出类型错误
if (x === promise2) {
console.log('======');
return reject(new TypeError('类型错误'));
}
// 允许状态改变一次
let called = false;
try {
//判断x是否包含then属性,thenable
if (typeof x === 'object' && x !== null) {
const then = x.then;
if (typeof then === 'function') {
// console.log(typeof then)
x.then(
x,
(v) => {
if (called) return;
called = true;
resolvePromise(v, promise2, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
if (called) return;
called = true;
resolve(x);
}
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
}
class Promise {
constructor(exectuor) {
try {
//捕获执行器错误
exectuor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
status = PENDING;
value = null;
reason = null;
//存储失败和成功的回调
onFullFilledCallbacks = [];
onRejectedCallbacks = [];
static all(args) {
return new Promise((resolve, reject) => {
args.reduce((prev, curr, i, arr) => {
if (curr instanceof Promise) {
curr.then(
(v) => {
prev[i] = v;
if (prev.length === arr.length) {
resolve(prev);
}
},
(r) => {
reject(r);
}
);
} else {
prev[i] = curr;
}
return prev;
}, []);
});
}
static resolve(v) {
if (v instanceof Promise) return v;
return new Promise((resolve, reject) => resolve(v));
}
static reject(r) {
return new Promise((resolve, reject) => {
reject(r);
});
}
static allSettled(args) {
return new Promise((resolve, reject) => {
function addData(prev, index, value) {
prev[index] = value;
if (prev.length === args.length) {
resolve(prev);
}
}
args.reduce((prev, curr, index, arr) => {
if (curr instanceof Promise) {
curr.then(
(res) => {
addData(prev, index, {
value: res,
status: 'fulfilled',
});
},
(r) => {
addData(prev, index, {
reason: r,
status: 'rejected',
});
}
);
} else {
addData(prev, index, {
reason: curr,
status: 'fulfilled',
});
}
});
});
}
static race(args) {
return new Promise((resolve, reject) => {
args.forEach((item) => {
if (item instanceof Promise) {
item.then(
(v) => {
resolve(v);
},
(r) => {
reject(r);
}
);
} else {
resolve(item);
}
});
});
}
finally(cb) {
return this.then(
(v) => {
return Promise.resolve(cb()).then(() => v);
},
(r) => {
return Promise.resolve(cb()).then(() => {
throw r;
});
}
);
}
resolve = (v) => {
//只有状态为pending才执行
if (this.status === PENDING) {
this.status = RESOLVED;
this.value = v;
this.onFullFilledCallbacks.forEach((c) => c());
}
};
reject = (r) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = r;
this.onRejectedCallbacks.forEach((c) => c());
}
};
then(onFullFilled, onRejected) {
debugger;
//onFullFilled onRejected类型判断
if (typeof onFullFilled !== 'function') onFullFilled = (v) => v;
if (typeof onFullFilled !== 'function') {
onRejected = (r) => {
throw r;
};
}
const promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
// Promise为微任务,所以放到微任务队列执行
queueMicrotask(() => {
try {
const x = onFullFilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === REJECTED) {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === PENDING) {
//如果状态为pending,则执行方法放入数组中,等待resolve或reject时候执行
this.onFullFilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFullFilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2;
}
}
Promise.deferred = function () {
var result = {};
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = Promise;
总结
- new Promise 的时候并且传递了回调参数
- Promise 类初始化,初始化了 promise 状态、记录成功失败的返回值、记录成功和失败的回调函数
- 此时开始执行 exectuor,源码中判断 exectuor 是否是函数,是,立即调用了 exectuor,不是直接 reject,并且把传递了 resolve 函数和 reject 函数
- 此时证明了 promise 的回调参数(函数)是同步的
- then 函数
默认返回 promise,保证可以链式调用
then 的回调执行是异步的,不管 Promise 的回调中是否开启了异步,在 then 中 onFullFilled,和 onRejected 都是通过 queueMicrotask 创建一个微任务队列去执行的,所以 then 的回调都是异步的
并且在 then 函数中进行了状态的判断,所以 promise 的状态只能从 pending => fulfilled 或 pending => rejected,并且状态是不可逆的
- 如果状态是 RESOLVED 通过创建 queueMicrotask 创建一个微任务队列去执行,如果进入 then 中状态变成了 RESOLVED 是因为是 promise 中直接调用了 resolve()
- 如果状态是 REJECTED 通过创建 queueMicrotask 创建一个微任务队列去执行,如果进入 then 中状态变成了 RESOLVED 是因为是 promise 中直接调用了 reject()
- 如果状态是 pending,能够进入 pending 实际上是因为 Promise 中进行了异步的处理,异步结果是不确定,所以将 onFullFilled/onRejected 通过创建微任务队列存放到 onFullFilledCallbacks 和 onRejectedCallbacks 数组中(因为有可能 promise 实例多次执行 then),等异步完成调用 resolve 或者 reject 在数组中依次循环取出执行
考虑 then 中手动返回 promise
有一个 resolvePromise 方法,这个方法会得到 onFullFilled(then 的成功回调)返回值,在该方法内部判断返回值是否为 promise
- 如果不是 promise 调用 resolve(备注:then 默认返回的 promise),将值传递到下一个 then 中
- 如果是 promise 有可能 promise 中调用 resolve 继续返回 promise 所以,继续递归调用 resolvePromise,一直到 x 不是 promise,就再次走上一条了
- 所以不管我们在 then 的成功回调用有没有返回 promise,实际上下一个 then 都是由上一个 then 默认返回的 promise 中的 resolve 返回的结果
generator 函数
Generator 函数是 ES6 提供的一种异步编程解决方案,作用是函数执行时可以进行暂停
- 形式上: Generator 函数是一个普通的函数,不过相对于普通函数多出了两个特征。一是在 function 关键字和函数明之间多了’*'号;二是函数内部使用了 yield 表达式,用于定义 Generator 函数中的每个状态。
- 语法上: Generator 函数封装了多个内部状态(通过 yield 表达式定义内部状态)。执行 Generator 函数时会返回一个遍历器对象(Iterator(迭代器)对象)。也就是说,Generator 是遍历器对象生成函数,函数内部封装了多个状态。通过返回的 Iterator 对象,可以依次遍历(调用 next 方法)Generator 函数的每个内部状态。
- 调用上: 普通函数在调用之后会立即执行,而 Generator 函数调用之后不会立即执行,而是会返回遍历器对象(Iterator 对象)。通过 Iterator 对象的 next 方法来遍历内部 yield 表达式定义的每一个状态。
generator 函数的使用
function* myGenerator() {
yield 'Hello';
yield 'world';
return 'ending';
}
let MG = myGenerator();
MG.next(); // {value:'Hello',done:false} value是yield后面的值
MG.next(); // {value:'world',done:false}
MG.next(); // {value:'ending',done:true}
MG.next(); // {value:'undefined',done:false}
遍历器对象的 next 方法的运行逻辑如下:
- 遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。
- 下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式。
- 如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值。
- 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined。
需要注意的是,yield 表达式后面的表达式,只有当调用 next 方法、内部指针指向该语句时才会执行 *
next 方法的参数
yield 表达式本身没有返回值,或者说总是返回 undefined。next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
function* foo(x) {
var y = 2 * (yield x + 1);
var z = yield y / 3;
return x + y + z;
}
var a = foo(5);
a.next(); // Object{value:6, done:false}
a.next(); // Object{value:NaN, done:false}
a.next(); // Object{value:NaN, done:true}
var b = foo(5);
b.next(); // { value:6, done:false } 第一个next相当于是调用函数
b.next(12); // { value:8, done:false }
b.next(13); // { value:42, done:true }
注意,由于 next 方法的参数表示上一个 yield 表达式的返回值,所以在第一次使用 next 方法时,传递参数是无效的。V8 引擎直接忽略第一次使用 next 方法时的参数,只有从第二次使用 next 方法开始,参数才是有效的。从语义上讲,第一个 next 方法用来启动遍历器对象,所以不用带有参数。
Thunk 函数
Thunk 函数在 Javascript 中,目的就是将多参数函数(入参中包含了 callback 函数)变成单参数版本的函数。而且单参数只能是 callback 函数。
//正常nodejs 中读取文件的函数
fs.readFile('data1.json', 'utf-8', (err, data) => {
// 获取文件内容
});
//封装成thunk函数
const thunk = function (fileName, codeType) {
// 返回一个只接受 callback 参数的函数
return function (callback) {
fs.readFile(fileName, codeType, callback);
};
};
const readFileThunk = thunk('data1.json', 'utf-8');
readFileThunk((err, data) => {
// 获取文件内容
});
上面代码的封装,是我们手动来做的,还可以直接使用第三方的 thunkify 库。
npm i thunkify --save
const thunkify = require('thunkify')
const thunk = thunkify(fs.readFile)
const readFileThunk = thunk('data1.json', 'utf-8')
readFileThunk((err, data) => {
// 获取文件内容
})
Thunk 最大的作用就是⽤于 Generator 函数的⾃动流程管理。参考链接
promise + generator + co 库
function* fn() {
yield new Promise((resolve) => {
setTimeout(() => {
console.log(1);
resolve();
}, 2000);
});
yield new Promise((resolve) => {
setTimeout(() => {
console.log(2);
resolve();
}, 1000);
});
yield new Promise((resolve) => {
setTimeout(() => {
console.log(3);
resolve();
}, 3000);
});
}
const gen = fn();
gen.next().value.then(() => {
gen.next().value.then(() => {
gen.next();
});
});
改成递归方式调用
function co(gen) {
const res = gen.next();
if (res.done) return;
res.value.then(() => {
co(gen);
});
}
但是此时没有考虑 co 的返回值为 promise, 所以 co 方法还需返回 promise,一旦返回 promise 又要考虑成功和失败的结果,此时直接使用 co 库。
co 库用于 Generator 函数的自动执行
npm i co
import co from 'co'
co(gen)
async/await
ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数是什么?一句话,它就是 Generator 函数的语法糖。参考链接
async 函数对 Generator 函数的改进,体现在以下四点:
- 内置执行器。
Generator 函数的执行必须靠执行器,所以才有了 co 模块,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更好的语义。
async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。
co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
- 返回值是 Promise。
async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。
async function f() {
return 'hello world';
}
f().then((v) => console.log(v));
// "hello world"