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