[TOC]

Promise

houdunren.com (opens new window)@ 向军大叔
JavaScript 中存在很多异步操作,Promise 将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。可以通过链式调用多个 Promise 达到我们的目的。
Promise 在各种开源库中已经实现,现在标准化后被浏览器默认支持。
promise 是一个拥有 then 方法的对象或函数

#问题探讨

下面通过多个示例来感受一下不使用 promise 时,处理相应问题的不易,及生成了不便阅读的代码。

#定时嵌套

下面是一个定时器执行结束后,执行另一个定时器,这种嵌套造成代码不易阅读



#图片加载

下面是图片后设置图片边框,也需要使用回调函数处理,代码嵌套较复杂
function loadImage(file, resolve, reject) {
const image = new Image();
image.src = file;
image.onload = () => {
resolve(image);
};
image.onerror = () => {
reject(new Error(“load fail”));
};
document.body.appendChild(image);
}

loadImage(
“images/houdunren.png”,
image => {
image.style.border = “solid 5px red”;
},
error => {
console.log(error);
}
);

#加载文件

下面是异步加载外部JS文件,需要使用回调函数执行,并设置的错误处理的回调函数
function load(file, resolve, reject) {
const script = document.createElement(“script”);
script.src = file;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
}
load(
“js/hd.js”,
script => {
console.log(${script.path[0].src} 加载成功);
hd();
},
error => {
console.log(${error.srcElement.src} 加载失败);
}
);
实例中用到的 hd.js 与 houdunren.js 内容如下
# hd.js
function hd() {
console.log(“hd function run”);
}

houdunren.js
function houdunren() {
console.log(“houdunren function run”);
hd();
}
如果要加载多个脚本时需要嵌套使用,下面houdunren.js 依赖 hd.js,需要先加载hd.js 后加载houdunren.js
不断的回调函数操作将产生回调地狱,使代码很难维护
load(
“js/hd.js”,
script => {
load(
“js/houdunren.js”,
script => {
houdunren();
},
error => {
console.log(${error.srcElement.src} 加载失败);
}
);
},
error => {
console.log(${error.srcElement.src} 加载失败);
}
);

#异步请求

使用传统的异步请求也会产生回调嵌套的问题,下在是获取向军的成绩,需要经过以下两步

  1. 根据用户名取得 向军 的编号
  2. 根据编号获取成绩

示例中用到的 php 文件请在 版本库 (opens new window)中查看
启动PHP服务器命令 php -S localhost:8080
function ajax(url, resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(“GET”, url);
xhr.send();
xhr.onload = function() {
if (this.status == 200) {
resolve(JSON.parse(this.response));
} else {
reject(this);
}
};
}
ajax(“http://localhost:8888/php/user.php?name=向军“, user => {
ajax(
http://localhost:8888/php/houdunren.php?id=${user["id"]},
response => {
console.log(response[0]);
}
);
});

#肯德基

下面是模拟肯德基吃饭的事情,使用 promise 操作异步的方式每个阶段会很清楚
let kfc = new Promise((resolve, reject) => {
console.log(“肯德基厨房开始做饭”);
resolve(“我是肯德基,你的餐已经做好了”);
});
let dad = kfc.then(msg => {
console.log(收到肯德基消息: ${msg});
return {
then(resolve) {
setTimeout(() => {
resolve(“孩子,我吃了两秒了,不辣,你可以吃了”);
}, 2000);
}
};
});
let son = dad.then(msg => {
return new Promise((resolve, reject) => {
console.log(收到爸爸消息: ${msg});
setTimeout(() => {
resolve(“妈妈,我和向军爸爸吃完饭了”);
}, 2000);
});
});
let ma = son.then(msg => {
console.log(收到孩子消息: ${msg},事情结束);
});
而使用以往的回调方式,就会让人苦不堪言
function notice(msg, then) {
then(msg);
}
function meal() {
notice(“肯德基厨房开始做饭”, msg => {
console.log(msg);
notice(“我是肯德基,你的餐已经做好”, msg => {
console.log(收到肯德基消息: ${msg});
setTimeout(() => {
notice(“孩子,我吃了两秒了,不辣,你可以吃了”, msg => {
console.log(收到爸爸消息: ${msg});
setTimeout(() => {
notice(“妈妈,我和向军爸爸吃完饭了”, msg => {
console.log(收到孩子消息: ${msg},事情结束);
});
}, 2000);
});
}, 2000);
});
});
}
meal();

#异步状态

Promise 可以理解为承诺,就像我们去KFC点餐服务员给我们一引取餐票,这就是承诺。如果餐做好了叫我们这就是成功,如果没有办法给我们做出食物这就是拒绝。

  • 一个 promise 必须有一个 then 方法用于处理状态改变

    #状态说明

    Promise包含pending、fulfilled、rejected三种状态

  • pending 指初始等待状态,初始化 promise 时的状态

  • resolve 指已经解决,将 promise 状态设置为fulfilled
  • reject 指拒绝处理,将 promise 状态设置为rejected
  • promise 是生产者,通过 resolve 与 reject 函数告之结果
  • promise 非常适合需要一定执行时间的异步任务
  • 状态一旦改变将不可更改

promise 是队列状态,就像体育中的接力赛,或多米诺骨牌游戏,状态一直向后传递,当然其中的任何一个promise也可以改变状态。
promise 没有使用 resolve 或 reject 更改状态时,状态为 pending
console.log(
new Promise((resolve, reject) => {
});
); //Promise {}
当更改状态后
console.log(
new Promise((resolve, reject) => {
resolve(“fulfilled”);
})
); //Promise {: “fulfilled”}

console.log(
new Promise((resolve, reject) => {
reject(“rejected”);
})
); //Promise {: “rejected”}
promise 创建时即立即执行即同步任务,then 会放在异步微任务中执行,需要等同步任务执行后才执行。
let promise = new Promise((resolve, reject) => {
resolve(“fulfilled”);
console.log(“后盾人”);
});
promise.then(msg => {
console.log(msg);
});
console.log(“houdunren.com”);
promise操作都是在其他代码后执行,下面会先输出houdunren.com再弹出success

  • promise 的 then、catch、finally的方法都是异步任务
  • 程序需要将主任务执行完成才会执行异步队列任务

const promise = new Promise(resolve => resolve(“success”));
promise.then(alert);
alert(“houdunren.com”);
promise.then(() => {
alert(“后盾人”);
});
下例在三秒后将 Promise 状态设置为 fulfilled ,然后执行 then 方法
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“fulfilled”);
}, 3000);
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);
状态被改变后就不能再修改了,下面先通过resolve 改变为成功状态,表示promise 状态已经完成,就不能使用 reject 更改状态了
new Promise((resolve, reject) => {
resolve(“操作成功”);
reject(new Error(“请求失败”));
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);

#动态改变

下例中p2 返回了p1 所以此时p2的状态已经无意义了,后面的then是对p1状态的处理。
const p1 = new Promise((resolve, reject) => {
// resolve(“fulfilled”);
reject(“rejected”);
});
const p2 = new Promise(resolve => {
resolve(p1);
}).then(
value => {
console.log(value);
},
reason => {
console.log(reason);
}
);
如果 resolve 参数是一个 promise ,将会改变promise状态。
下例中 p1 的状态将被改变为 p2 的状态
const p1 = new Promise((resolve, reject) => {
resolve(
//p2
new Promise((s, e) => {
s(“成功”);
})
);
}).then(msg => {
console.log(msg);
});
当promise做为参数传递时,需要等待promise执行完才可以继承,下面的p2需要等待p1执行完成。

  • 因为p2 的resolve 返回了 p1 的promise,所以此时p2 的then 方法已经是p1 的了
  • 正因为以上原因 then 的第一个函数输出了 p1 的 resolve 的参数

const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“操作成功”);
}, 2000);
});
const p2 = new Promise((resolve, reject) => {
resolve(p1);
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);

#then

一个promise 需要提供一个then方法访问promise 结果,then 用于定义当 promise 状态发生改变时的处理,即promise处理异步操作,then 用于结果。
promise 就像 kfc 中的厨房,then 就是我们用户,如果餐做好了即 fulfilled ,做不了拒绝即rejected 状态。那么 then 就要对不同状态处理。

  • then 方法必须返回 promise,用户返回或系统自动返回
  • 第一个函数在resolved 状态时执行,即执行resolve时执行then第一个函数处理成功状态
  • 第二个函数在rejected状态时执行,即执行reject 时执行第二个函数处理失败状态,该函数是可选的
  • 两个函数都接收 promise 传出的值做为参数
  • 也可以使用catch 来处理失败的状态
  • 如果 then 返回 promise ,下一个then 会在当前promise 状态改变后执行

    #语法说明

    then的语法如下,onFulfilled 函数处理 fulfilled 状态, onRejected函数处理 rejected 状态

  • onFulfilled 或 onRejected 不是函数将被忽略

  • 两个函数只会被调用一次
  • onFulfilled 在 promise 执行成功时调用
  • onRejected 在 promise 执行拒绝时调用

promise.then(onFulfilled, onRejected)

#基础知识

then 会在 promise 执行完成后执行,then 第一个函数在 resolve成功状态执行
const promise = new Promise((resolve, reject) => {
resolve(“success”);
}).then(
value => {
console.log(解决:${value});
},
reason => {
console.log(拒绝:${reason});
}
);
then 中第二个参数在失败状态执行
const promise = new Promise((resolve, reject) => {
reject(“is error”);
});
promise.then(
msg => {
console.log(成功:${msg});
},
error => {
console.log(失败:${error});
}
);
如果只关心成功则不需要传递 then 的第二个参数
const promise = new Promise((resolve, reject) => {
resolve(“success”);
});
promise.then(msg => {
console.log(成功:${msg});
});
如果只关心失败时状态,then 的第一个参数传递 null
const promise = new Promise((resolve, reject) => {
reject(“is error”);
});
promise.then(null, error => {
console.log(失败:${error});
});
promise 传向then的传递值,如果then没有可处理函数,会一直向后传递
let p1 = new Promise((resolve, reject) => {
reject(“rejected”);
})
.then()
.then(
null,
f => console.log(f)
);
如果 onFulfilled 不是函数且 promise 执行成功, p2 执行成功并返回相同值
let promise = new Promise((resolve, reject) => {
resolve(“resolve”);
});
let p2 = promise.then();
p2.then().then(resolve => {
console.log(resolve);
});
如果 onRejected 不是函数且promise拒绝执行,p2 拒绝执行并返回相同值
let promise = new Promise((resolve, reject) => {
reject(“reject”);
});
let p2 = promise.then(() => {});
p2.then(null, null).then(null, reject => {
console.log(reject);
});

#链式调用

每次的 then 都是一个全新的 promise,默认 then 返回的 promise 状态是 fulfilled
let promise = new Promise((resolve, reject) => {
resolve(“fulfilled”);
}).then(resolve => {
console.log(resolve);
})
.then(resolve => {
console.log(resolve);
});
每次的 then 都是一个全新的 promise,不要认为上一个 promise状态会影响以后then返回的状态
let p1 = new Promise(resolve => {
resolve();
});
let p2 = p1.then(() => {
console.log(“后盾人”);
});
p2.then(() => {
console.log(“houdunren.com”);
});
console.log(p1); // Promise {}
console.log(p2); // Promise {}

再试试把上面两行放在 setTimeout里
setTimeout(() => {
console.log(p1); // Promise {}
console.log(p2); // Promise {}
});
then 是对上个promise 的rejected 的处理,每个 then 会是一个新的promise,默认传递 fulfilled状态
new Promise((resolve, reject) => {
reject();
})
.then(
resolve => console.log(“fulfilled”),
reject => console.log(“rejected”)
)
.then(
resolve => console.log(“fulfilled”),
reject => console.log(“rejected”)
)
.then(
resolve => console.log(“fulfilled”),
reject => console.log(“rejected”)
);

# 执行结果如下
ejected
fulfilled
fulfilled
如果内部返回 promise 时将使用该 promise
let p1 = new Promise(resolve => {
resolve();
});
let p2 = p1.then(() => {
return new Promise(r => {
r(“houdunren.com”);
});
});
p2.then(v => {
console.log(v); //houdunren.com
});
如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理,需要等待该 promise 变更状态后执行。
let promise = new Promise(resolve => resolve());
let p1 = promise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(p1);
resolve();
}, 2000);
});
}).then(() => {
return new Promise((a, b) => {
console.log(p2);
});
});
如果then返回 promise 时,返回的promise 后面的then 就是处理这个promise 的
如果不 return 情况就不是这样了,即外层的 then 的promise 和内部的promise 是独立的两个promise
new Promise((resolve, reject) => {
resolve();
})
.then(v => {
return new Promise((resolve, reject) => {
resolve(“第二个promise”);
}).then(value => {
console.log(value);
return value;
});
})
.then(value => {
console.log(value);
});
这是对上面代码的优化,把内部的 then 提取出来
new Promise((resolve, reject) => {
resolve();
})
.then(v => {
return new Promise((resolve, reject) => {
resolve(“第二个promise”);
});
})
.then(value => {
console.log(value);
return value;
})
.then(value => {
console.log(value);
});

#其它类型

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [Resolve],如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

#循环调用

如果 then 返回与 promise 相同将禁止执行
let promise = new Promise(resolve => {
resolve();
});
let p2 = promise.then(() => {
return p2;
}); // TypeError: Chaining cycle detected for promise

#promise

如果返加值是 promise 对象,则需要更新状态后,才可以继承执行后面的promise
new Promise((resolve, reject) => {
resolve(
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“解决状态”);
}, 2000);
})
);
})
.then(
v => {
console.log(fulfilled: ${v});
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(“失败状态”);
}, 2000);
});
},
v => {
console.log(rejected: ${v});
}
)
.catch(error => console.log(rejected: ${error}));

#Thenables

包含 then 方法的对象就是一个 promise ,系统将传递 resolvePromise 与 rejectPromise 做为函数参数
下例中使用 resolve 或在then 方法中返回了具有 then方法的对象

  • 该对象即为 promise 要先执行,并在方法内部更改状态
  • 如果不更改状态,后面的 then promise都为等待状态

new Promise((resolve, reject) => {
resolve({
then(resolve, reject) {
resolve(“解决状态”);
}
});
})
.then(v => {
console.log(fulfilled: ${v});
return {
then(resolve, reject) {
setTimeout(() => {
reject(“失败状态”);
}, 2000);
}
};
})
.then(null, error => {
console.log(rejected: ${error});
});
包含 then 方法的对象可以当作 promise来使用
class User {
constructor(id) {
this.id = id;
}
then(resolve, reject) {
resolve(ajax(http://localhost:8888/php/houdunren.php?id=${this.id}));
}
}
new Promise((resolve, reject) => {
resolve(ajax(http://localhost:8888/php/user.php?name=向军));
})
.then(user => {
return new User(user.id);
})
.then(lessons => {
console.log(lessons);
});
当然也可以是类
new Promise((resolve, reject) => {
resolve(
class {
static then(resolve, reject) {
setTimeout(() => {
resolve(“解决状态”);
}, 2000);
}
}
);
}).then(
v => {
console.log(fulfilled: ${v});
},
v => {
console.log(rejected: ${v});
}
);
如果对象中的 then 不是函数,则将对象做为值传递
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return {
then: “后盾人”
};
})
.then(v => {
console.log(v); //{then: “后盾人”}
});

#catch

下面使用未定义的变量同样会触发失败状态
let promise = new Promise((resolve, reject) => {
hd;
}).then(
value => console.log(value),
reason => console.log(reason)
);
如果 onFulfilled 或 onRejected 抛出异常,则 p2 拒绝执行并返回拒因
let promise = new Promise((resolve, reject) => {
throw new Error(“fail”);
});
let p2 = promise.then();
p2.then().then(null, resolve => {
console.log(resolve + “,后盾人”);
});
catch用于失败状态的处理函数,等同于 then(null,reject){}

  • 建议使用 catch 处理错误
  • 将 catch 放在最后面用于统一处理前面发生的错误

const promise = new Promise((resolve, reject) => {
reject(new Error(“Notice: Promise Exception”));
}).catch(msg => {
console.error(msg);
});
catch 可以捕获之前所有 promise 的错误,所以建议将 catch 放在最后。下例中 catch 也可以捕获到了第一个 then 返回 的 promise 的错误。
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return new Promise((resolve, reject) => {
reject(“.then “);
});
})
.then(() => {})
.catch(msg => {
console.log(msg);
});
错误是冒泡的操作的,下面没有任何一个then 定义第二个函数,将一直冒泡到 catch 处理错误
new Promise((resolve, reject) => {
reject(new Error(“请求失败”));
})
.then(msg => {})
.then(msg => {})
.catch(error => {
console.log(error);
});
catch 也可以捕获对 then 抛出的错误处理
new Promise((resolve, reject) => {
resolve();
})
.then(msg => {
throw new Error(“这是then 抛出的错误”);
})
.catch(() => {
console.log(“33”);
});
catch也可以捕获其他错误,下面在then中使用了未定义的变量,将会把错误抛出到catch
new Promise((resolve, reject) => {
resolve(“success”);
})
.then(msg => {
console.log(a);
})
.catch(reason => {
console.log(reason);
});

#使用建议

建议将错误要交给catch处理而不是在then中完成,不建议使用下面的方式管理错误
new Promise((resolve, reject) => {
reject(new Error(“请求失败”));
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);

#处理机制

在 promise 中抛出的错误也会被catch 捕获
const promise = new Promise((resolve, reject) => {
throw new Error(“fail”);
}).catch(msg => {
console.log(msg.toString()+”后盾人”);
});
可以将上面的理解为如下代码,可以理解为内部自动执行 try…catch
const promise = new Promise((resolve, reject) => {
try {
throw new Error(“fail”);
} catch (error) {
reject(error);
}
}).catch(msg => {
console.log(msg.toString());
});
但像下面的在异步中 throw 将不会触发 catch,而使用系统错误处理
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error(“fail”);
}, 2000);
}).catch(msg => {
console.log(msg + “后盾人”);
});
下面在then 方法中使用了没有定义的hd函数,也会抛除到 catch 执行,可以理解为内部自动执行 try…catch
const promise = new Promise((resolve, reject) => {
resolve();
})
.then(() => {
hd();
})
.catch(msg => {
console.log(msg.toString());
});
在 catch 中发生的错误也会抛给最近的错误处理
const promise = new Promise((resolve, reject) => {
reject();
})
.catch(msg => {
hd();
})
.then(null, error => {
console.log(error);
});

#定制错误

可以根据不同的错误类型进行定制操作,下面将参数错误与404错误分别进行了处理
class ParamError extends Error {
constructor(msg) {
super(msg);
this.name = “ParamError”;
}
}
class HttpError extends Error {
constructor(msg) {
super(msg);
this.name = “HttpError”;
}
}
function ajax(url) {
return new Promise((resolve, reject) => {
if (!/^http/.test(url)) {
throw new ParamError(“请求地址格式错误”);
}
let xhr = new XMLHttpRequest();
xhr.open(“GET”, url);
xhr.send();
xhr.onload = function() {
if (this.status == 200) {
resolve(JSON.parse(this.response));
} else if (this.status == 404) {
// throw new HttpError(“用户不存在”);
reject(new HttpError(“用户不存在”));
} else {
reject(“加载失败”);
}
};
xhr.onerror = function() {
reject(this);
};
});
}

ajax(http://localhost:8888/php/user.php?name=后盾人)
.then(value => {
console.log(value);
})
.catch(error => {
if (error instanceof ParamError) {
console.log(error.message);
}
if (error instanceof HttpError) {
alert(error.message);
}
console.log(error);
});

#事件处理

unhandledrejection事件用于捕获到未处理的Promise错误,下面的 then 产生了错误,但没有catch 处理,这时就会触发事件。该事件有可能在以后被废除,处理方式是对没有处理的错误直接终止。
window.addEventListener(“unhandledrejection”, function(event) {
console.log(event.promise); // 产生错误的promise对象
console.log(event.reason); // Promise的reason
});

new Promise((resolve, reject) => {
resolve(“success”);
}).then(msg => {
throw new Error(“fail”);
});

#finally

无论状态是resolve 或 reject 都会执行此动作,finally 与状态无关。
const promise = new Promise((resolve, reject) => {
reject(“hdcms”);
})
.then(msg => {
console.log(“resolve”);
})
.catch(msg => {
console.log(“reject”);
})
.finally(() => {
console.log(“resolve/reject状态都会执行”);
});
下面使用 finally 处理加载状态,当请求完成时移除加载图标。请在后台php文件中添加 sleep(2); 设置延迟响应


loading…


#实例操作

#异步请求

下面是将 ajax 修改为 promise 后,代码结构清晰了很多
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(“GET”, url);
xhr.send();
xhr.onload = function() {
if (this.status == 200) {
resolve(JSON.parse(this.response));
} else {
reject(this);
}
};
});
}

ajax(“http://localhost:8888/php/user.php?name=向军“)
.then(user =>ajax(http://localhost:8888/php/houdunren.php?id=${user["id"]}))
.then(lesson => {
console.log(lesson);
});

#图片加载

下面是异步加载图片示例
function loadImage(file) {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = file;
image.onload = () => {
resolve(image);
};
image.onerror = reject;
document.body.appendChild(image);
});
}

loadImage(“images/houdunren.png”).then(image => {
image.style.border = “solid 20px black”;
console.log(“宽度:” + window.getComputedStyle(image).width);
});

#定时器

下面是封装的timeout 函数,使用定时器操作更加方便
function timeout(times) {
return new Promise(resolve => {
setTimeout(resolve, times);
});
}

timeout(3000)
.then(() => {
console.log(“3秒后执行”);
return timeout(1000);
})
.then(() => {
console.log(“执行上一步的promise后1秒执行”);
});
封闭 setInterval 定时器并实现动画效果




#链式操作

  • 第个 then 都是一个promise
  • 如果 then 返回 promse,只当promise 结束后,才会继承执行下一个 then

    #语法介绍

    下面是对同一个 promise 的多个 then ,每个then 都得到了同一个promise 结果,这不是链式操作,实际使用意义不大。
    const promise = new Promise((resolve, reject) => {
    resolve(“后盾人”);
    });
    promise.then(hd => {
    hd += “-hdcms”;
    console.log(hd); //后盾人-hdcms
    });
    promise.then(hd => {
    hd += “-houdunren”;
    console.log(hd); //后盾人-houdunren
    });
    第一个 then 也是一个promise,当没接受到结果是状态为 pending
    const promise = new Promise((resolve, reject) => {
    resolve(“后盾人”);
    });

console.log(
promise.then(hd => {
hd += “-hdcms”;
console.log(hd);
})
); //Promise {}
promise 中的 then 方法可以链接执行,then 方法的返回值会传递到下一个then 方法。

  • then 会返回一个promise ,所以如果有多个then 时会连续执行
  • then 返回的值会做为当前promise 的结果

下面是链式操作的 then,即始没有 return 也是会执行,因为每个then 会返回promise
new Promise((resolve, reject) => {
resolve(“后盾人”);
})
.then(hd => {
hd += “-hdcms”;
console.log(hd); //后盾人-hdcms
return hd;
})
.then(hd => {
hd += “-houdunren”;
console.log(hd); //后盾人-hdcms-houdunren
});
then 方法可以返回一个promise 对象,等promise 执行结束后,才会继承执行后面的 then。后面的then 方法就是对新返回的promise 状态的处理
new Promise((resolve, reject) => {
resolve(“第一个promise”);
})
.then(msg => {
console.log(msg);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“第二个promise”);
}, 3000);
});
})
.then(msg => {
console.log(msg);
});

#链式加载

使用promise 链式操作重构前面章节中的文件加载,使用代码会变得更清晰
function load(file) {
return new Promise((resolve, reject) => {
const script = document.createElement(“script”);
script.src = file;
script.onload = () => resolve(script);
script.onerror = () => reject();
document.body.appendChild(script);
});
}

load(“js/hd.js”)
.then(() => load(“js/houdunren.js”))
.then(() => houdunren());

#操作元素

下面使用 promise 对元素事件进行处理


第九章 闭包与作用域




#async/await

使用 async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。

  • async/await 本质还是promise,只是更简洁的语法糖书写
  • async/await 使用更清晰的promise来替换 promise.then/catch 的方式

    #async

    下面在 hd 函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。
    async function hd() {
    return “houdunren.com”;
    }
    console.log(hd());
    hd().then(value => {
    console.log(value);
    });
    如果有多个await 需要排队执行完成,我们可以很方便的处理多个异步队列
    async function hd(message) {
    return new Promise(resolve => {
    setTimeout(() => {
    resolve(message);
    }, 2000);
    });
    }
    async function run() {
    let h1 = await hd(“后盾人”);
    console.log(h1);
    let h2 = await hd(“houdunren.com”);
    console.log(h2);
    }
    run();

    #await

    使用 await 关键词后会等待promise 完

  • await 后面一般是promise,如果不是直接返回

  • await 必须放在 async 定义的函数中使用
  • await 用于替代 then 使编码更优雅

下例会在 await 这行暂停执行,直到等待 promise 返回结果后才继执行。
async function hd() {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“houdunren.com”);
}, 2000);
});
let result = await promise;
console.log(result);
}
hd()
一般await后面是外部其它的promise对象
async function hd() {
return new Promise(resolve => {
setTimeout(() => {
resolve(“fulfilled”);
}, 2000);
});
}
async function run() {
let value = await hd();
console.log(“houdunren.com”);
console.log(value);
}
run();
下面是请求后台获取用户课程成绩的示例
async function user() {
let user = await ajax(http://localhost:8888/php/user.php?name=向军);
let lessons = await ajax(
http://localhost:8888/php/houdunren.php?id=${user.id}
);
console.log(lessons);
}
也可以将操作放在立即执行函数中完成
(async () => {
let user = await ajax(http://localhost:8888/php/user.php?name=向军);
let lessons = await ajax(
http://localhost:8888/php/houdunren.php?id=${user.id}
);
console.log(lessons);
})();
下面是使用async 设置定时器,并间隔时间来输出内容
async function sleep(ms = 2000) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function run() {
for (const value of [“后盾人”, “向军”]) {
await sleep();
console.log(value);
}
}
run();

#加载进度

下面是请求后台加载用户并通过进度条展示的效果





#类中使用

和 promise 一样,await 也可以操作thenables 对象
class User {
constructor(name) {
this.name = name;
}
then(resolve, reject) {
let user = ajax(http://localhost:8888/php/user.php?name=${this.name});
resolve(user);
}
}
async function get() {
let user = await new User(“向军”);
console.log(user);
}
get();
类方法也可以通过 async 与 await 来操作promise
class User {
constructor() {}
async get(name) {
let user = await ajax(
http://localhost:8888/php/user.php?name=${name}
);
user.name += “-houdunren.com”;
return user;
}
}
new User().get(“向军”).then(resolve => {
console.log(resolve);
});

#其他声明

函数声明
async function get(name) {
return await ajax(http://localhost:8888/php/user.php?name=${name});
}
get(“后盾人”).then(user => {
console.log(user);
});
函数表达式
let get = async function(name) {
return await ajax(http://localhost:8888/php/user.php?name=${name});
};
get(“后盾人”).then(user => {
console.log(user);
});
对象方法声明
let hd = {
async get(name) {
return await ajax(http://localhost:8888/php/user.php?name=${name});
}
};

hd.get(“后盾人”).then(user => {
console.log(user);
});
立即执行函数
(async () => {
let user = await ajax(http://localhost:8888/php/user.php?name=向军);
let lessons = await ajax(
http://localhost:8888/php/houdunren.php?id=${user.id}
);
console.log(lessons);
})();
类方法中的使用
class User {
async get(name) {
return await ajax(http://localhost:8888/php/user.php?name=${name});
}
}
let user = new User().get(“后盾人”).then(user => {
console.log(user);
});

#错误处理

async 内部发生的错误,会将必变promise对象为rejected 状态,所以可以使用catch 来处理
async function hd() {
console.log(houdunren);
}
hd().catch(error => {
throw new Error(error);
});
下面是异步请求数据不存在时的错误处理
async function get(name) {
return await ajax(http://localhost:8888/php/user.php?name=${name});
}

get(“向军小哥”).catch(error => {
alert(“用户不存在”);
});
如果promise 被拒绝将抛出异常,可以使用 try…catch 处理错误
async function get(name) {
try {
let user = await ajax(
http://localhost:8888/php/user.php?name=${name}
);
console.log(user);
} catch (error) {
alert(“用户不存在”);
}
}
get(“向军老师”);
多个 await 时当前面的出现失败,后面的将不可以执行
async function hd() {
await Promise.reject(“fail”);
await Promise.resolve(“success”).then(value => {
console.log(value);
});
}
hd();
如果对前一个错误进行了处理,后面的 await 可以继续执行
async function hd() {
await Promise.reject(“fail”).catch(e => console.log(e));
await Promise.resolve(“success”).then(value => {
console.log(value);
});
}
hd();
也可以使用 try…catch 特性忽略不必要的错误
async function hd() {
try {
await Promise.reject(“fail”);
} catch (error) {}
await Promise.resolve(“success”).then(value => {
console.log(value);
});
}
hd();
也可以将多个 await 放在 try…catch 中统一处理错误
async function hd(name) {
const host = “http://localhost:8888/php“;
try {
const user = await ajax(${host}/user.php?name=${name});
const lessons = await ajax(${host}/user.php?id=${user.id});
console.log(lessons);
} catch (error) {
console.log(“用户不存在”);
}
}
hd(“后盾人教程”);

#并发执行

有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待
async function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log(“houdunren”);
resolve();
}, 2000);
});
}
async function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log(“hdcms”);
resolve();
}, 2000);
});
}
async function hd() {
await p1();
await p2();
}
hd();
使用 Promise.all() 处理多个promise并行执行
async function hd() {
await Promise.all([p1(), p2()]);
}
hd();
让promise先执行后再使用await处理结果
async function hd() {
let h1 = p1();
let h2 = p2();
await h1;
await h2;
}
hd();