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);}