ES6 Promise 对象 – 闪电教程JSRUN
概述
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。它由社区最早提出和实现,ES6将其写进了语言规范,对立了用法,原生提供了Promise对象。
所谓Promise,简略说就是一个容器,外面保留着某个将来才会完结的事件(通常是一个异步操作)的后果。从语法上说,Promise 是一个对象,从它能够获取异步操作的音讯。Promise 提供对立的 API,各种异步操作都能够用同样的办法进行解决。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已实现,又称 Fulfilled)和Rejected(已失败)。只有异步操作的后果,能够决定以后是哪一种状态,任何其余操作都无奈扭转这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,示意其余伎俩无奈扭转。
(2)一旦状态扭转,就不会再变,任何时候都能够失去这个后果。Promise对象的状态扭转,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只有这两种状况产生,状态就凝固了,不会再变了,会始终放弃这个后果。如果扭转曾经产生了,你再对Promise对象增加回调函数,也会立刻失去这个后果。这与事件(Event)齐全不同,事件的特点是,如果你错过了它,再去监听,是得不到后果的。
有了Promise对象,就能够将异步操作以同步操作的流程表达出来,防止了层层嵌套的回调函数。此外,Promise对象提供对立的接口,使得管制异步操作更加容易。
Promise也有一些毛病。首先,无奈勾销Promise,一旦新建它就会立刻执行,无奈中途勾销。其次,如果不设置回调函数,Promise外部抛出的谬误,不会反馈到内部。第三,当处于Pending状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
如果某些事件一直地重复产生,一般来说,应用 stream 模式是比部署Promise更好的抉择。
根本应用
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
上面代码发明了一个Promise实例。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作胜利 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数承受一个函数作为参数,该函数的两个参数别离是resolve和reject。
resolve函数的作用是,将Promise对象的状态从“未实现”变为“胜利”(即从Pending变为Resolved),在异步操作胜利时调用,并将异步操作的后果,作为参数传递进来;reject函数的作用是,将Promise对象的状态从“未实现”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的谬误,作为参数传递进来。
Promise实例生成当前,能够用then办法别离指定Resolved状态和Reject状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then办法能够承受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不肯定要提供。这两个函数都承受Promise对象传出的值作为参数。
上面是一个Promise对象的简略例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
下面代码中,timeout办法返回一个Promise实例,示意一段时间当前才会产生的后果。过了指定的工夫(ms参数)当前,Promise实例的状态变为Resolved,就会触发then办法绑定的回调函数。
示例
<script>
//原始操作
//1、查出以后用户信息
//2、依照以后用户的id查出他的课程
//3、依照以后课程id查出分数
$.ajax({
url: "mock/user.json",
success(data) {
console.log("查问用户:", data);
$.ajax({
url: `mock/user_corse_${data.id}.json`,
success(data) {
console.log("查问到课程:", data);
$.ajax({
url: `mock/corse_score_${data.id}.json`,
success(data) {
console.log("查问到分数:", data);
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
},
error(error) {
console.log("出现异常了:" + error);
}
});
</script>
应用promise
//1、Promise能够封装异步操作
let p = new Promise((resolve, reject) => { //传入胜利解析,失败回绝
//1、异步操作
$.ajax({
url: "mock/user.json",
success: function (data) {
console.log("查问用户胜利:", data)
resolve(data);
},
error: function (err) {
reject(err);
}
});
});
p.then((obj) => { //胜利当前做什么
return new Promise((resolve, reject) => {
$.ajax({
url: `mock/user_corse_${obj.id}.json`,
success: function (data) {
console.log("查问用户课程胜利:", data)
resolve(data);
},
error: function (err) {
reject(err)
}
});
})
}).then((data) => { //胜利当前干什么
console.log("上一步的后果", data)
$.ajax({
url: `mock/corse_score_${data.id}.json`,
success: function (data) {
console.log("查问课程得分胜利:", data)
},
error: function (err) {
}
});
})
封装promise
function get(url, data) { //本人定义一个办法整合一下
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
success: function (data) {
resolve(data);
},
error: function (err) {
reject(err)
}
})
});
}
get("mock/user.json")
.then((data) => {
console.log("用户查问胜利~~~:", data)
return get(`mock/user_corse_${data.id}.json`);
})
.then((data) => {
console.log("课程查问胜利~~~:", data)
return get(`mock/corse_score_${data.id}.json`);
})
.then((data)=>{
console.log("课程问题查问胜利~~~:", data)
})
.catch((err)=>{ //失败的话catch
console.log("出现异常",err)
});
async 函数
ES2017 规范引入了 async 函数,使得异步操作变得更加不便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
ES6 Generator 函数的异步利用 – 闪电教程JSRUN
应用 Generator 函数,顺次读取两个文件。
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
写成async函数,就是上面这样。
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比拟就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
一比拟就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改良,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与一般函数截然不同,只有一行。
var result = asyncReadFile();
下面的代码调用了asyncReadFile函数,而后它就会主动执行,输入最初后果。这齐全不像 Generator 函数,须要调用next办法,或者用co模块,能力真正执行,失去最初后果。
(2)更好的语义。
async和await,比起星号和yield,语义更分明了。async示意函数里有异步操作,await示意紧跟在前面的表达式须要期待后果。
(3)更广的适用性。
co模块约定,yield命令前面只能是 Thunk 函数或 Promise 对象,而async函数的await命令前面,能够是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象不便多了。你能够用then办法指定下一步的操作。
进一步说,async函数齐全能够看作多个异步操作,包装成的一个 Promise 对象,而await命令就是外部then命令的语法糖
根本用法
async函数返回一个 Promise 对象,能够应用then办法增加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作实现,再接着执行函数体内前面的语句。
上面是一个例子,指定多少毫秒后输入一个值。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
下面代码指定50毫秒当前,输入hello world。
因为async函数返回的是 Promise 对象,能够作为await命令的参数。所以,下面的例子也能够写成上面的模式。
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async 函数有多种应用模式。
// 函数申明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的办法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的办法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
语法
返回 Promise 对象
async函数返回一个 Promise 对象。
async函数外部return语句返回的值,会成为then办法回调函数的参数。
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
下面代码中,函数f外部return命令返回的值,会被then办法回调函数接管到。
async函数外部抛出谬误,会导致返回的 Promise 对象变为reject状态。抛出的谬误对象会被catch办法回调函数接管到。
Promise 对象的状态变动
async函数返回的 Promise 对象,必须等到外部所有await命令前面的 Promise 对象执行完,才会产生状态扭转,除非遇到return语句或者抛出谬误。也就是说,只有async函数外部的异步操作执行完,才会执行then办法指定的回调函数。
上面是一个例子。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
下面代码中,函数getTitle外部有三个操作:抓取网页、取出文本、匹配页面题目。只有这三个操作全副实现,才会执行then办法外面的console.log。
await 命令
失常状况下,await命令前面是一个 Promise 对象。如果不是,会被转成一个立刻resolve的 Promise 对象。
async function f() {
return await 123;
}
f().then(v => console.log(v))
// 123
下面代码中,await命令的参数是数值123,它被转成 Promise 对象,并立刻resolve。
await命令前面的 Promise 对象如果变为reject状态,则reject的参数会被catch办法的回调函数接管到。
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
留神,下面代码中,await语句后面没有return,然而reject办法的参数仍然传入了catch办法的回调函数。这里如果在await后面加上return,成果是一样的。
只有一个await语句前面的 Promise 变为reject,那么整个async函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
下面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
有时,咱们心愿即便前一个异步操作失败,也不要中断前面的异步操作。这时能够将第一个await放在try…catch构造外面,这样不论这个异步操作是否胜利,第二个await都会执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
另一种办法是await前面的 Promise 对象再跟一个catch办法,解决后面可能呈现的谬误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
应用留神点
第一点,后面曾经说过,await命令前面的Promise对象,运行后果可能是rejected,所以最好把await命令放在try…catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
};
}
第二点,多个await命令前面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
下面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比拟耗时,因为只有getFoo实现当前,才会执行getBar,齐全能够让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
下面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行工夫。
第三点,await命令只能用在async函数之中,如果用在一般函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
下面代码会报错,因为await用在一般函数之中了。然而,如果将forEach办法的参数改成async函数,也有问题。
function dbFuc(db) { //这里不须要 async
let docs = [{}, {}, {}];
// 可能失去谬误后果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
下面代码可能不会失常工作,起因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采纳for循环。
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
如果的确心愿多个申请并发执行,能够应用Promise.all办法。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者应用上面的写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
发表回复