Promise对象、Generator 函数、async函数

一、Promise对象的含义、特点

含义: 异步编程的一种解决方案,传统解决方案通过回调函数和时间,而Promise相当于一个容器,里面保存着未来才会结束的事件(通常是一个异步操作)的结果,从语法上说,Promise是一个对象,可以获取异步操作的消息,提供统一的API,各种异步操作都可以以同样的方法进行处理
特点:(1)对象的状态不受外界影响,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以改变当前的状态,其他任何操作都无法改变这个状态。
(2)状态一旦改变就不会在变了,任何时候都可以得到这个结果Promise对象的状态改变只有两种可能,从pendingfulfilled,从pendingrejected,只要状态发生改变,就会一直保持这个结果,就算再对对象Promise添加回调函数,也会立即得到这个结果,与事件Event不同,事件的特带你是如果错过了它,再去监听,是得不到结果的
(3)Promise一旦新建就会立即执行,无法途中取消,如果设置回调函数,Promise内部抛出错误,不会反应到外部,不会影响整体函数的执行,处于pending状态时,无法得知当前进展哪一个阶段

基本用法

在这里插入图片描述
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

        let promise=new Promise(function (resolve,reject){
            setTimeout(resolve,100,1);
        });

Promise构造函数接受一个函数作为参数,该函数的两个参数也是一个函数,分别是resolvedrejectedresolvedPromise对象的状态从未完成变为成功,成功时的回调函数,会将异步操作的结果,作为参数传递出去rejectedPromise对象的状态由未完成变为失败,失败时调用的函数,也会将异步操作报出的错误,作为参数传递出去

注:setTimeout(resolve,100,1);,第三个参数时第一个参数函数的的形参

        promise.then(function (res){
            console.log(res);//1
        },function (arr){
            console.log(arr);
        }).catch(function (err){
            console.log(err);
        })

then方法接受两个回调函数作为参数,第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数Promise对象变为rejected时调用,这个函数是可选的,不一定要提供,这两个函数都接受Promise对象传出的值作为参数Promise对象的状态发生改变时就会触发then方法绑定的回调函数
then()方法的作用是Promise实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数then()方法会返回一个新的Promise实例所以then()方法后面可以继续跟另一个then()方法进行链式调用

        //promise执行的先后顺序
        let promise = new Promise(function (resolve, reject) {
            console.log("start");
            setTimeout(resolve, 1000, "shuju");
        });
        console.log("外部");
        promise.then(function (res) {
            console.log(res);
            //对于res的操作需要在此代码块写,因为外部获取不到
        });
        console.log("wan");

在这里插入图片描述
图中的数据是其他代码的结果,忽略掉,从结果看就是,Promise一旦新建就立即执行,输入start,与此同时输入同步结果外部、完,当setTimeout(resolve, 1000, "shuju");执行完后,即Promise状态改变后,resolve函数的结果作为形参传入then(),并输出结果!
异步加载图片

        let promise = new Promise(function (resolve, reject) {
            let img = new Image();
            img.onload = function () {
                resolve(img);
            };
            img.onerror = function () {
                reject(new Error("图片加载失败" + url));
            }
        });
        promise.then(function (res) {
            console.log(res);
        }).catch(function (err) {
            console.log(err);
        })

Promise对象实现的 Ajax 操作

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

多个Promise连用

        const promise1=new Promise(function(resolve,reject){
           setTimeout(resolve,3000,"a");
        })
        const promise2=new Promise(function(resolve,reject){
            setTimeout(resolve,1000,promise1);
        })
        promise2.then(function(res){
            console.log(res);
        })

在这里插入图片描述
promise1promise2都是 Promise 的实例,但是promise2resolve方法将promise1作为参数,即一个异步操作的结果是返回另一个异步操作,promise1的状态就会传递给promise2promise2的回调函数就会等待promise1执行完,状态改变后再执行
再来一个例子

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个 Promise,3 秒之后变为rejectedp2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

多个then()链式调用

var p2 = new Promise(function (resolve) {
    resolve(100);
});
p2.then(function (value) {
    return value * 2;
}).then(function (value) {
    return value * 2;
}).then(function (value) {
    console.log("finally: " + value);
});

调用then()方法之后都会新建一个promise对象,这里的return语句相当于Promise语句中的resolve()或者rejected(),他们的结果,作为then方法函数的参数传入

多个Promise合并
Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1p2p3返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了

上面代码中,p1resolvedp2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了

Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

二、Generator 函数的语法

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象。Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

Generator 函数有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态,星号*的位置以下几个都是合法的

//*的位置
//建议使用这一个
function* sum(x){
    yield x+(yield x*2);
    yield x+3;
}

function * sum(x){
    yield x+(yield x*2);
    yield x+3;
}

function  *sum(x){
    yield x+(yield x*2);
    yield x+3;
}

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。

function* c(){
   yield 'hello';
   yield 'world';
   return 'over'; 
}
console.log(c);/*ƒ* c(){
   yield 'hello';
   yield 'world';
   return 'over'; 
}*/
console.log(c());

在这里插入图片描述

总结:调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。yield表达式是暂停执行的标记,而next方法可以恢复执行

function* c(){
   yield 'hello';
   yield 'world';
   return 'over'; 
}
console.log(c);/*ƒ* c(){
   yield 'hello';
   yield 'world';
   return 'over'; 
}*/
console.log(c());
var d=c();
console.log(d.next());//{value: "hello", done: false}
console.log(d.next());//{value: "world", done: false}
console.log(d.next());//{value: "over", done: true}

//遍历结束的时候,即done为true时,以后的next()  value值都为undefined
console.log(d.next());//{value: undefined, done: true}
console.log(d.next());//{value: undefined, done: true}

遍历器对象的next方法的运行逻辑如下

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”的语法功能

function *sum1(){
    yield 12+34;
}
console.log(sum1().next());//46

yield表达式与return语句既有相似之处,也有区别
相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return

复杂一点的例子

function * sum(x){
    yield x+(yield x*2);
}
var sums=sum(3);
//第一种
console.log(sums.next());//6 false
console.log(sums.next());//NAN false
console.log(sums.next());//undefined true

//第二种
console.log(sums.next());//6 false
console.log(sums.next(90));//93 false
console.log(sums.next());//undefined true



世纪大难题!!!被自己写的函数难住了!!!
这里需要注意的是

yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

for…of循环
for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

一旦next方法的返回对象的done属性为truefor...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

yield*表达式

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
let delegatedIterator = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());

let delegatingIterator = (function* () {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye.';
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
//yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。

function* concat(iter1, iter2) {
  yield* iter1;
  yield* iter2;
}

// 等同于

function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

三、async函数

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
async函数的返回值是 Promise 对象
async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

例子:下面代码指定 50 毫秒以后,输出hello world

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

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函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误,也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

最好把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();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

unction 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方法。当三个请求都会resolved时,下面两种写法效果相同。

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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章