引言
promise 主要解决了回调地域, 也就是嵌套太深的 callback, 而采用链式方式. 如:
// 普通方式
$.get({
url:'url',
success:function(){
$.get({
url:'url2',
success:function(){
$.get({
url:'url3',
success:function(){
// ...
}
})
}
})
}
})
// promise , 假如 $.get 支持 promise 方式
$.get({
url:'url1'
})
.then(res=>{
return $.get({
url :'url2'
})
})
.then(res=>{
console.log('ok')
})
.catch(()=>{
console.log('error')
})
Promise 对象
- 构造 Promise 实例时, 参数为一个函数
var p1 = new Promise(function(resolve,reject){
console.log('promise start')
})
// Promise 内部类似
function Promise(fn){
var resolve = function(){}
var reject = function(){}
fn(resolve,reject);
return this;
}
- then, 收集 resolve 后要执行的回调 ; catch , 收集 reject 后要执行的回调
var p1 = new Promise(function(resolve,reject){
console.log('promise start')
})
p1.then(function(){
console.log(1)
})
.then(function(){
console.log(2)
})
// Promise 内部类似
function Promise(fn){
var resolve = ()=>{
this.thenList.forEach((fn)=>{
fn();
})
}
var reject = ()=>{
this.catchList.forEach((fn)=>{
fn();
})
}
this.thenList = [] ;
this.catchList = [];
fn(resolve,reject);
this.then = function(callback){
this.thenList.push(callback);
return this;
}
this.catch = function(callback){
this.catchList.push(callback) ;
return this;
}
return this;
}
实践发现 , 并不能打印出 1 , 因为 resolve 的执行, 是早于 then 的调用, 这个时候的 thenList 还是个空数组. 所以修改一下, 先让 then 执行. 也就是利用事件循环的原理.
function Promise(fn){
var resolve = ()=>{
setTimeout(()=>{
this.thenList.forEach((fn) => {
fn();
})
},0)
};
var reject = ()=>{
setTimeout(()=>{
this.catchList.forEach((fn) => {
fn();
})
},0)
};
// 其他不变
}
- 而还有一个要注意的点就是 , then 的时候, 是可以返回一个新的 Promise 对象, 打断当前链条的. 而返回其他非 Promise 对 then 的链式无影响.
如下, 并未在返回新的 Promise 未打断原 Promise 的 then 链式:
var p1 = new Promise(function (resolve, reject) {
console.log('promise start')
resolve();
})
.then(function () {
console.log(1)
return new Promise(function (resolve, reject) {
reject();
})
})
.then(function () {
console.log(2)
})
.catch(function () {
console.log('error')
})
// 应该打印 : promise start , 1 , error
可以通过对每次的 then 和 catch 做返回判断, 如果返回的是 Promise 对象, 则停止之前的 then 和 catch 执行, 将剩余未执行的拼在返回的 Promise 的对象的原有 thenList 和 catchList 后面 .
这里顺便也加上参数和状态.
function Promise(fn) {
this.status = 'Pending';
var resolve = (...args) => {
this.status = 'Resolved';
setTimeout(() => {
for (var i = 0; i < this.thenList.length; i++) {
var result = this.thenList[i](...args);
if (result instanceof Promise) {
result.thenList = [...result.thenList, ...this.thenList.slice(i + 1)]
result.catchList = [...result.catchList, ...this.catchList]
break;
}
}
})
}
var reject = (...args) => {
this.status = 'Rejected';
setTimeout(() => {
for (var i = 0; i < this.catchList.length; i++) {
var result = this.catchList[i](...args);
if (result instanceof Promise) {
result.thenList = [...result.thenList, ...this.thenList]
result.catchList = [...result.catchList, ...this.catchList.slice(i + 1)]
break;
}
}
})
}
this.thenList = [];
this.catchList = [];
fn(resolve, reject);
this.then = function (callback) {
this.thenList.push(callback);
return this;
}
this.catch = function (callback) {
this.catchList.push(callback);
return this;
}
return this;
}
需要特别说明的是: 这里虽然借用 setTimeout 实现 , 但 promise 和 setTimeout 在事件循环中的表现还是有差异的 , promise 是生成微任务 jobs, 而 setTimeout 则是生成宏任务 , 也就是 task , 每次的 task 是一个任务 , 一个完整的任务包括 jobs .
async / await
async 和 await 只是 promise 的语法糖, 其核心还是 promise .
既然是糖, 吃多了对牙口不好.
- await 替代 promise 的 then 的写法
使用 await 可以省略 then 的链式写法 , 但是 await 必须在 async “装饰” 的函数体内. 也就是 await 不能写在全局作用域内.
// 常规方式新建 promise 对象
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve('hello') },5000)
})
// 常规 promise then 调用
p1.then(result =>{ console.log('normal',result) })
// await 方式全局作用域调用
// var result = await p1; // 报错 Uncaught SyntaxError: await is only valid in async function
// await 正常调用 => 在一个 async 修饰的函数内
async function test(){
console.log('async start')
var result = await p1;
console.log('await',result)
}
test();
console.log('end');
// 打印 async start , end , normal hello , await hello
从上述例子, 可以得出以下几点:
1. await 等的是一个 promise 的 resolve 的结果
2. await 只能在一个 async 修饰的函数体内
3. await 时, 不会堵塞 await 所在的 async 的函数体之外的 js 执行
所以通俗 await 做了哪些事
// 常规写法
var result ;
p1.then(res=>{
result = res
console.log(result);
})
// await 写法
var result = await p1;
console.log(result);
还有就是 await 可以不只是等 promise , 也可以等一个同步函数
function sayHi(){
return 'hi';
}
async function test(){
var result = await sayHi();
console.log(result);
}
test();
console.log('end');
// 打印结果: end , hi
可以看出, 虽然 await 的是一个同步执行, 直接返回了 hi , 但是却是在 end 后面输出; 由此可以大胆猜测 , await 的作用就是把它后面的代码都放到了 promise 的 then 中
- async 修饰一个函数返回 promise 对象
// 常规创建 promise 对象
var p1 = new Promise((resolve,reject)=>{
resolve('hello');
})
// async 创建 promise 对象
// 1. 无返回 , 则是 resolve(undefined)
var p2 = (async function b(){
})();
// 2. 有返回, 则是 resolve(返回值)
var p3 = (async function c(){
return 'hi';
})();
// 调用
p1.then(res=>{console.log(res)});
p2.then(res=>{console.log(res)});
p3.then(res=>{console.log(res)});
console.log('end');
// 打印 : end , hello , undefined , hi
注意:
1. async 用来 “修饰” 函数, 但该函数并不是 promise , 而是该函数执行后, 返回一个 promise 对象.
2. async “修饰” 的函数有返回值, 则作为 resolve 的参数返回, 无返回值或者无返回, 则 undefined 作为 resolve 的结果返回.
async 和 promise 的 resolve 的一点差别
// 常规 promise 的延时返回
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve('hi') },5000);
})
// await 如果照常规写法返回
var p2 = (async function b(){
setTimeout(()=>{ return ('hi')},5000)
})();
p1.then(res=>{ console.log(res) }); // 5 秒后打印 hi
p2.then(res=>{ console.log(res) }); // undefined
// 打印结果: undefined , 5秒后打印 hi
// 那么怎么实现 async 中的 延迟效果? 也就是说 d 函数需要等 (await) 5秒, 再返回出去
var p3 = (async function d(){
await wait(5000);
return 'hi';
})();
function wait(s){
return new Promise((resolve,reject)=>{
setTimeout(()=>{ resolve() },s)
})
}
p3.then(res=>{ console.log(res) });
那么, 从上面可以看出什么端倪?
1. async “修饰” 的函数, 也还是一个同步函数, 也就是它急切地完成, 急切地需要一个结果, 急切地包装成一个 promise 对象并返回. 所有 p2 他不会等异步的定时器走完, 返回 ‘hi’ , 才结束一个函数. 直接跳过异步事件, 走完整个函数, 找到返回值包装返回.
2. async 并不能代替 new Promise 做事情, 主要作用是用来 , 框定一部分异步代码 (await) 逻辑 ,使得其按 promise 的 then 方式顺序执行, 又不至于影响到范围之外的同步代码的执行.
await 和 async 总结
- await 只能存在 async 修饰的函数体内
- await 后面的代码, 都将等待 await 的 promise 的 resolve , 也就是堵塞的.
- async 单独用处不大, 主要用来配合 await .
目前综合来看 , await 和 async 的组合的主要作用, 是解决了 then 的存在和 then 的冗长的链式调用. 然后通过局部变量接收 promise 的 then 的结果, 方便后面的调用, 避免了 多个 then 需要传参的尴尬.
await 的错误捕获 和 Promise.all 等后面专门总结一下.
总结
以上 Promise 模拟实现, 并非 js 内置实现的 Promise , 只是用简单的代码模拟 , 强调 Promise 其中的几个特性.
- Promise 是一个函数.
- Promise 的参数是一个函数 ( fn ), 且在生成 Promise 实例时, 这个函数会立即执行.
- Promise 的 then 和 catch 的参数也是函数, 在生成 Promise 实例时, 并不会直接执行函数 , 但是会挂载在 Promise 实例上 .
- Promise 传入的 fn 执行 resolve 或 reject 时 , 这时候的 Promise 实例必然已经是初始化完成, 即 then 和 catch 都已经挂载完毕.
- 第 4 条 , 也就是说明 resolve 和 reject 触发 的 then 和 catch 在同步代码执行之后 . 而且实际上是在 setTimeout 之前.
- 在 then 和 catch 中可以返回一个新的 Promise , 打断之前 Promise 链.
研究新的事物, 可以从两个方面出发:
- 用旧有的已经掌握的知识来推导新知识, 以及建立联系
- 控制变量法, 每次只研究其中一小部分, 其他保持不变.