javascript:Promise 对象

原文章:https://wangdoc.com/javascript/index.html

Promise 对象

概述

Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。

注意,本章只是 Promise 对象的简单介绍。为了避免与后续教程的重复,更完整的介绍请看《ES6 标准入门》《Promise 对象》一章。

首先,Promise 是一个对象,也是一个构造函数。

function f1(resolve, reject) {
  // 异步代码...
}

var p1 = new Promise(f1);

上面代码中,Promise构造函数接受一个回调函数f1作为参数,f1里面是异步操作的代码。然后,返回的p1就是一个 Promise 实例。

Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。Promise 实例有一个then方法,用来指定下一步的回调函数。

var p1 = new Promise(f1);
p1.then(f2);

上面代码中,f1的异步操作执行完成,就会执行f2

传统的写法可能需要把f2作为回调函数传入f1,比如写成f1(f2),异步操作完成后,在f1内部调用f2。Promise 使得f1f2变成了链式写法。不仅改善了可读性,而且对于多层嵌套的回调函数尤其方便。

// 传统写法
step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // ...
      });
    });
  });
});

// Promise 的写法
(new Promise(step1))
  .then(step2)
  .then(step3)
  .then(step4);

从上面代码可以看到,采用 Promises 以后,程序流程变得非常清楚,十分易读。注意,为了便于理解,上面代码的Promise实例的生成格式,做了简化,真正的语法请参照下文。

总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promise 就是解决这个问题,使得异步流程可以写成同步流程。

Promise 原本只是社区提出的一个构想,一些函数库率先实现了这个功能。ECMAScript 6 将其写入语言标准,目前 JavaScript 原生支持 Promise 对象。

Promise 对象的状态

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

  • 异步操作未完成(pending)
  • 异步操作成功(fulfilled)
  • 异步操作失败(rejected)

上面三种状态里面,fulfilledrejected合在一起称为resolved(已定型)。

这三种的状态的变化途径只有两种。

  • 从“未完成”到“成功”
  • 从“未完成”到“失败”

一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种。

  • 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled
  • 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected

Promise 构造函数

JavaScript 提供原生的Promise构造函数,用来生成 Promise 实例。

var promise = new Promise(function (resolve, reject) {
  // ...

  if (/* 异步操作成功 */){
    resolve(value);
  } else { /* 异步操作失败 */
    reject(new Error());
  }
});

上面代码中,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。

resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

下面是一个例子。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100)

上面代码中,timeout(100)返回一个 Promise 实例。100毫秒以后,该实例的状态会变为fulfilled

Promise.prototype.then()

Promise 实例的then方法,用来添加回调函数。

then方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled状态)的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。

var p1 = new Promise(function (resolve, reject) {
  resolve('成功');
});
p1.then(console.log, console.error);
// "成功"

var p2 = new Promise(function (resolve, reject) {
  reject(new Error('失败'));
});
p2.then(console.log, console.error);
// Error: 失败

上面代码中,p1p2都是Promise 实例,它们的then方法绑定两个回调函数:成功时的回调函数console.log,失败时的回调函数console.error(可以省略)。p1的状态变为成功,p2的状态变为失败,对应的回调函数会收到异步操作传回的值,然后在控制台输出。

then方法可以链式使用。

p1
  .then(step1)
  .then(step2)
  .then(step3)
  .then(
    console.log,
    console.error
  );

上面代码中,p1后面有四个then,意味依次有四个回调函数。只要前一步的状态变为fulfilled,就会依次执行紧跟在后面的回调函数。

最后一个then方法,回调函数是console.logconsole.error,用法上有一点重要的区别。console.log只显示step3的返回值,而console.error可以显示p1step1step2step3之中任意一个发生的错误。举例来说,如果step1的状态变为rejected,那么step2step3都不会执行了(因为它们是resolved的回调函数)。Promise 开始寻找,接下来第一个为rejected的回调函数,在上面代码中是console.error。这就是说,Promise 对象的报错具有传递性。

then() 用法辨析

Promise 的用法,简单说就是一句话:使用then方法添加回调函数。但是,不同的写法有一些细微的差别,请看下面四种写法,它们的差别在哪里?

// 写法一
f1().then(function () {
  return f2();
});

// 写法二
f1().then(function () {
  f2();
});

// 写法三
f1().then(f2());

// 写法四
f1().then(f2);

为了便于讲解,下面这四种写法都再用then方法接一个回调函数f3。写法一的f3回调函数的参数,是f2函数的运行结果。

f1().then(function () {
  return f2();
}).then(f3);

写法二的f3回调函数的参数是undefined

f1().then(function () {
  f2();
  return;
}).then(f3);

写法三的f3回调函数的参数,是f2函数返回的函数的运行结果。

f1().then(f2())
  .then(f3);

写法四与写法一只有一个差别,那就是f2会接收到f1()返回的结果。

f1().then(f2)
  .then(f3);

实例:图片加载

下面是使用 Promise 完成图片的加载。

var preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    var image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

上面代码中,image是一个图片对象的实例。它有两个事件监听属性,onload属性在图片加载成功后调用,onerror属性在加载失败调用。

上面的preloadImage()函数用法如下。

preloadImage('https://example.com/my.jpg')
  .then(function (e) { document.body.append(e.target) })
  .then(function () { console.log('加载成功') })

上面代码中,图片加载成功以后,onload属性会返回一个事件对象,因此第一个then()方法的回调函数,会接收到这个事件对象。该对象的target属性就是图片加载后生成的 DOM 节点。

小结

Promise 的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。

而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。这意味着,无论何时为 Promise 实例添加回调函数,该函数都能正确执行。所以,你不用担心是否错过了某个事件或信号。如果是传统写法,通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。

Promise 的缺点是,编写的难度比传统写法高,而且阅读代码也不是一眼可以看懂。你只会看到一堆then,必须自己在then的回调函数里面理清逻辑。

微任务

Promise 的回调函数属于异步任务,会在同步任务之后执行。

new Promise(function (resolve, reject) {
  resolve(1);
}).then(console.log);

console.log(2);
// 2
// 1

上面代码会先输出2,再输出1。因为console.log(2)是同步任务,而then的回调函数属于异步任务,一定晚于同步任务执行。

但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。

setTimeout(function() {
  console.log(1);
}, 0);

new Promise(function (resolve, reject) {
  resolve(2);
}).then(console.log);

console.log(3);
// 3
// 2
// 1

上面代码的输出结果是321。这说明then的回调函数的执行时间,早于setTimeout(fn, 0)。因为then是本轮事件循环执行,setTimeout(fn, 0)在下一轮事件循环开始时执行。

参考链接

原文地址:https://www.cnblogs.com/wbyixx/p/12499162.html

时间: 2024-10-07 07:55:14

javascript:Promise 对象的相关文章

Javascript Promise对象学习

ES6中的Promise对象 var p = new Promise(function(resolve, reject){ window.setTimeout(function(){ console.log("1",1); resolve(1); },10); }); p.then(function(a){ return new Promise(function(resolve,reject){ window.setTimeout(function(){ console.log(&qu

JavaScript 初识Promise 对象

什么是Promise? 其实, Promise就是一个类,而且这个类已经成为ES6的标准,是 ECMAScript 6 规范的重要特性之一.这个类目前在chrome32.Opera19.Firefox29以上的版本都已经支持了,要想在所有浏览器上都用上的话就看看es6-promise吧. ES6 的 Promises 是采用了 Promises/A+ 提案的一种实现.你现在能够找到的各种第三方实现,如果是完全兼容了 Promises/A+ 的,那么就和 ES6 的 Promises 是一致的(当

通过一道笔试题浅谈javascript中的promise对象

因为前几天做了一个promise对象捕获错误的面试题目,所以这几天又重温了一下promise对象.现在借这道题来分享下一些很基础的知识点. 下面是一个面试题目,三个promise对象捕获错误的例子,返回结果有什么不同. //使用throw添加错误事件 var p = new Promise(function(resolve, reject) { resolve("ok"); throw new Error('error0'); //setTimeout(function() { thr

浅谈Javascript中Promise对象的实现

https://segmentfault.com/a/1190000000684654 What? Promise是CommonJS的规范之一,拥有resolve.reject.done.fail.then等方法,能够帮助我们控制代码的流程,避免函数的多层嵌套.如今异步在web开发中越来越重要,对于开发人员来说,这种非线性执行的编程会让开发者觉得难以掌控,而Promise可以让我们更好地掌控代码的执行流程,jQuery等流行的js库都已经实现了这个对象,年底即将发布的ES6也将原生实现Promi

ECMAScript6的Promise对象

1. 概念 Promise对象用于异步(asynchronouss)计算,一个Promise对象代表着一个还未完成,但预期完成的操作. 2. 出现原因: 1)  如果你需要通过ajax发送多次请求,而每一次请求依赖上一次请求返回的结果作为参数来继续下次的请求,这样的话,就需要这么写代码: 上面的例子,假设请求C需要依赖B返回的数据,那么C需要放在B的success函数里面.同样的,A需要依赖B返回的数据,那么A也需要放在B的success函数里面.假设现在存在很多个请求,请求之前是相互的依赖关系

前端基本知识(四):JS的异步模式:1、回调函数;2、事件监听;3、观察者模式;4、promise对象

JavaScript语言将任务的执行模式可以分成两种:同步(Synchronous)和异步(Asychronous). “同步模式”就是一个任务完成之后,后边跟着一个任务接着执行:程序的执行顺序和排列顺序是一直的:”异步模式”则完全不同,每一个任务都有一个或者多个回调函数(callback),前一个任务结束的时候,不是执行下一个任务,二十执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务顺序不一致的,异步的. 在浏览器端,耗时时间长的操作都应该异步执行,避免浏览器数去

JavaScript Promise启示录

本篇,主要普及promise的用法. 一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心.在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路. 近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解. 在callback的模型里边,我们假

谈谈 ES6 的 Promise 对象

前言 开篇首先设想一个日常开发常常会遇到的需求:在多个接口异步请求数据,然后利用这些数据来进行一系列的操作.一般会这样去写: $.ajax({ url: '......', success: function (data) { $.ajax({ // 要在第一个请求成功后才可以执行下一步 url: '......', success: function (data) { // ...... } }); } }); 这样的写法的原理是,当执行一些异步操作时,我们需要知道操作是否已经完成,所有当执行

JavaScript Promise API

同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的新API都开始promise原理实现.下面让我们看一下什么是promise,以及它的API和用法! Promises现状 XMLHttpRequest API是异步的,但它没有使用promise API.但有很多原生的 javascript API 使用了promise: Battery API fetch