关于javascript:Promise-函数

7次阅读

共计 8843 个字符,预计需要花费 23 分钟才能阅读完成。

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);
}
正文完
 0