关于javascript:Promise-函数

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理