[ES6] promise & async/await

[ES6] promise & async/await

  • 为什么需要 promise 对象
  • promise 对象与函数的区别
  • Promise 对象两个特点
    • 对象的状态不受外界影响
    • 一旦状态改变,就不会再变
  • Promise 优缺点
    • Promise 优点
    • Promise 缺点
  • Promise 使用
    • Promise 构造函数
    • Promise 构造函数参数
    • promise 对象
    • promise 对象方法
      • Promise.prototype.then 方法
      • Promise.prototype.catch 方法:异常处理函数
      • Promise.prototype.finally 方法:最终的回调函数
      • Promise.race 方法
      • Promise.race 方法
      • Promise.resolve/reject 方法
    • asycn/await 指令
      • 定义
    • Promise 实现 Ajax 实例

ECMAscript 6 原生提供了 Promise 对象。

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

为什么需要 promise 对象

有时为了实现异步结束的后续操作,需要执行回调,但是当有多个异步操作时,可能导致回调嵌套过深,或者回调地狱

以前是通过"函数瀑布"实现的异步,例如:

setTimeout(function () {
  console.log("First");
  setTimeout(function () {
    console.log("Second");
    setTimeout(function () {
      console.log("Third");
    }, 3000);
  }, 4000);
}, 1000);

以上是一个比较简单的示例,但是在一个复杂的程序当中,用 “函数瀑布” 实现的程序,无论是维护还是异常处理都是特别繁琐的事情,而且会让缩进格式变得非常冗赘

new Promise(function (resolve, reject) {
  setTimeout(function () {
    console.log("First");
    resolve();
  }, 1000);
})
  .then(function () {
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        console.log("Second");
        resolve();
      }, 4000);
    });
  })
  .then(function () {
    setTimeout(function () {
      console.log("Third");
    }, 3000);
  });

以上嵌套的代码,变成了顺序格式的代码

promise 是为了更加优雅的书写异步任务

Promise 不是一种将异步转换为同步的方法,只不过是一种更良好的编程风格。

promise 对象与函数的区别

promise 对象能够保存状态,而函数不可以(闭包函数除外)

promise 主要是为了处理异步编程的情况

什么时候适合用 Promise 而不是传统回调函数?当需要多次顺序执行异步操作的时候,例如,通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合 Promise。

Promise 对象两个特点

对象的状态不受外界影响

Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • resolved: 已完成,意味着操作成功完成,也称之为 fulfilled
  • rejected: 已失败,意味着操作失败。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

一旦状态改变,就不会再变

一旦状态改变,就不会再变,对于当前 promise 对象任何时候都得到都是这个结果

Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对当前 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

Promise 优缺点

Promise 优点

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise 缺点

  1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。

  2. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。

  3. 当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise 使用

Promise 构造函数

要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化,下面是创建 promise 的步骤:

var promise = new Promise(function (resolve, reject) {
  // 异步处理
  // 处理结束后、调用resolve 或 reject
});

Promise 构造函数是 JavaScript 中用于创建 promise 对象的内置构造函数。

Promise 构造函数参数

Promise 构造函数接受一个函数作为参数,该函数是同步的并且会被立即执行,所以我们称之为起始函数

起始函数包含两个参数 resolve 和 reject,分别表示 Promise 成功和失败的状态:

  • 起始函数执行成功时,调用 resolve 函数并传递成功的结果
  • 起始函数执行失败时,调用 reject 函数并传递失败的原因

resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;

resolve 和 reject 并不能够使起始函数停止运行,所以需要 return

promise 对象

Promise 构造函数返回值是一个 Promise 对象, Promise 对象具有以下几个方法:

  • then:用于处理 Promise 成功状态的回调函数
  • catch:用于处理 Promise 失败状态的回调函数
  • finally:无论 Promise 是成功还是失败,都会执行的回调函数
  • 以上三个方法的参数均是函数
var promise = new Promise(function (resolve, reject) {
  //使用setTimeout(...)来模拟异步代码
  //实际编码时可能是XHR请求或是HTML5的一些API方法.
  setTimeout(function () {
    resolve("成功!"); //代码正常执行!
  }, 250);
});

promise.then(function (successMessage) {
  //successMessage是resolve(...)方法传入的值
  document.write("Yay! " + successMessage); //Yay!成功!
});

promise 对象方法

promise 对象可以调用 promise.then() 方法,promise.then() 是 promise 最为常用的方法:

promise.then(onResolved, onRejected)

  • onResolved 是 Promise 构造函数执行成功时的回调
  • onRejected 是 Promise 构造函数执行失败时的回调,
  • 以上两个函数只会有一个被调用

promise 对象简化了对 error 的处理,写法为:

promise.then(onResolved).catch(onRejected)

Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获

Promise.prototype.then 方法

  1. then 按顺序执行

Promise.prototype.then 方法可以多次调用,并按顺序执行

getJSON("/posts.json")
  .then(function (json) {
    return json.post;
  })
  .then(function (post) {
    // proceed
  });

上面的代码,getJSON 的 resolve 结果会作为参数传入第一个 then 函数,第一个 then 回调函数完成以后,会将返回结果作为参数,传入第二个 then 回调函数。

getJSON("/post/1.json")
  .then(function (post) {
    return getJSON(post.commentURL);
  })
  .then(function (comments) {
    // 对comments进行处理
  });

如果前一个 then 回调函数返回的是 Promise 对象,这时后一个回调函数就会等待该 Promise 对象有了运行结果,才会进一步调用。

  1. 链式调用

Promise 链式编程最好保持扁平化,不要嵌套 Promise

所以可以直接在 then 中返回一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作,用于链式调用,就不需要在 then 中嵌套 Promise 的回调

const p = new Promise(function (resolve, reject) {
  resolve(1);
})
  .then(function (value) {
    // 第一个then // 1
    console.log(value);
    return value * 2;
  })
  .then(function (value) {
    // 第二个then // 2
    console.log(value);
  })
  .then(function (value) {
    // 第三个then // undefined
    console.log(value);
    //链式调用,返回promise对象
    return Promise.resolve("resolve");
  })
  .then(function (value) {
    // 第四个then // resolve
    console.log(value);
    return Promise.reject("reject");
  })
  .then(
    function (value) {
      // 第五个then //接收到第四个then reject:reject,所以不打印
      console.log("resolve:" + value);
    },
    function (err) {
      //接收到 第四个then 的reject所以打印
      console.log("reject:" + err);
    }
  );

then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列

const p1 = new Promise(function (resolve, reject) {
  resolve(1);
})
  .then(function (result) {
    p2(result).then((newResult) => p3(newResult));
  })
  .then(() => p4());

链式编程,创建新 Promise 但忘记返回它时,对应链条被打破,导致 p4 会与 p2 和 p3 同时进行。

大多数浏览器中不能终止 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error))

注意,总是返回或终止 Promise 链

Promise.prototype.catch 方法:异常处理函数

Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。

getJSON("/posts.json")
  .then(function (posts) {
    // some code
  })
  .catch(function (error) {
    // 处理前一个回调函数运行时发生的错误
    console.log("发生错误!", error);
  });

Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。

catch 可以捕获 Promise 构造函数 reject 的异常,也可以捕获 then 中的错误

getJSON("/post/1.json")
  .then(function (post) {
    return getJSON(post.commentURL);
  })
  .then(function (comments) {
    // some code
  })
  .catch(function (error) {
    // 处理前两个回调函数的错误
  });

then 块如何中断?then 块默认会向下顺序执行,return 是不能中断的,可以通过 throw 来跳转至 catch,实现中断。

什么时候需要再写一个 then 而不是在当前的 then 接着编程? 当又需要调用一个异步任务的时候。

Promise.prototype.finally 方法:最终的回调函数

finally() 是在 Promise 执行的最后一定会执行的序列

new Promise(function (resolve, reject) {
  resolve(1);
})
  .then(function (value) {
    console.log(value);
    return value * 2;
  })
  .catch(function (error) {
    console.log(error);
  })
  .finally(function () {
    console.log(value);
  });

除了 then 块以外,catch/finally 能否多次使用?可以,finally 与 then 一样会按顺序执行,但是 catch 块只会执行第一个,除非 catch 块里有异常。所以最好只安排一个 catch 和 finally 块。

then、catch 和 finally 序列顺序可以颠倒但不建议这样做,最好按 then-catch-finally 的顺序编写程序


Promise.race 方法

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

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

p1、p2、p3 都是 Promise 对象的实例(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例)

p 的状态由 p1、p2、p3 决定,分成两种情况:

  1. 只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
  2. 只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

下面是一个具体的例子:

// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON("/post/" + id + ".json");
});

Promise.all(promises)
  .then(function (posts) {
    // ...
  })
  .catch(function (reason) {
    // ...
  });

Promise.race 方法

Promise.race 方法与 Promise.all 有些类似,同样是将多个 Promise 实例,包装成一个新的 Promise 实例

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

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

如果 Promise.all 方法和 Promise.race 方法的参数,不是 Promise 实例,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。


Promise.resolve/reject 方法

有时需要将现有对象转为 Promise 对象,Promise.resolve 方法就起到这个作用:

var jsPromise = Promise.resolve($.ajax("/whatever.json"));

上面代码将 jQuery 生成 deferred 对象,转为一个新的 ES6 的 Promise 对象。

如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为 fulfilled。

var p = Promise.resolve("Hello");

p.then(function (s) {
  console.log(s); // s="Hello",Hello
});

上面代码生成一个新的 Promise 对象的实例 p,它的状态为 fulfilled,所以回调函数会立即执行,Promise.resolve 方法的参数就是回调函数的参数。

如果 Promise.resolve 方法的参数是一个 Promise 对象的实例,则会被原封不动地返回。

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。Promise.reject 方法的参数 reason,会被传递给实例的回调函数。

var p = Promise.reject("出错了");
p.then(null, function (s) {
  console.log(s); // 出错了,s="出错了"
});

上面代码生成一个 Promise 对象的实例,状态为 rejected,回调函数会立即执行。Promise.reject 方法的参数就是回调函数的参数。

asycn/await 指令

const https = require("https");

function fetch(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      let data = "";
      res.on("data", (rd) => (data = data + rd));
      res.on("end", () => resolve(data));
      res.on("error", reject);
    });
  });
}
/* ------- 方式1 --------*/
fetch("https://www.javascript.com/").then((data) => {
  console.log(data.length);
});
/* ------- 方式2 --------*/
(async function read() {
  try {
    const data = await fetch("https://www.javascript.com/");
    console.log(data);
    console.log(data.length);
  } catch (err) {
    console.log(err); // 会输出 Some error
  }
})();

新的指令 asycn/await 的出现将减少链式调用的出现,也增加了代码的可读性,对于应用中出现彼此依赖的 promise 更加友好

该指令使异步操作变得像同步操作一样容易了,async 异步函数前可以使用 await 指令,await 指令后必须跟着一个 Promise 对象,也即 async 异步函数中必须包含 Promise 对象, 异步函数会在这个 Promise 对象运行中暂停,直到其运行结束再继续运行

await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用,否则报错

处理异常的机制将用 try-catch 块实现

async function asyncFunc() {
  let value = await new Promise(function (resolve, reject) {
    resolve("Return value");
  });
  console.log(value); //Return value
}
asyncFunc();

所以 Promise 对象的返回值,await 语句也会返回它

定义

async function name([param[, param[, ... param]]]) { statements }
  • name: 函数名称。
  • param: 要传递给函数的参数的名称。
  • statements: 函数体语句。
  • async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数
async function helloAsync() {
  return "helloAsync";
}
console.log(helloAsync()); // Promise {: "helloAsync"}
helloAsync().then((v) => {
  console.log(v); // helloAsync
});

await 针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
  • 非 Promise 对象:直接返回对应的值。

Promise 实现 Ajax 实例

Promise 对象实现的 Ajax 操作的实例:

function ajax(URL) {
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest();
    req.open("GET", URL, true);
    req.onload = function () {
      if (req.status === 200) {
        resolve(req.responseText);
      } else {
        reject(new Error(req.statusText));
      }
    };
    req.onerror = function () {
      reject(new Error(req.statusText));
    };
    req.send();
  });
}
var URL = "/try/ajax/testpromise.php";
ajax(URL)
  .then(function onResolved(value) {
    document.write("内容是:" + value);
  })
  .catch(function onRejected(error) {
    document.write("错误:" + error);
  });

上面代码中,onFulfilled修改成了onResolved,只是方法名的变更,并不影响函数的执行,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

var p1 = new Promise(function (resolve, reject) {
  // ... some code
});

var p2 = new Promise(function (resolve, reject) {
  // ... some code
  resolve(p1);
});

上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 resolved(fulfilled) 或者 rejected,那么 p2 的回调函数将会立刻执行。

你可能感兴趣的:(JS,es6,javascript,前端)