搜索
首页web前端js教程详细解读JavaScript编程中的Promise使用_基础知识

Promise核心说明

尽管Promise已经有自己的规范,但目前的各类Promise库,在Promise的实现细节上是有差异的,部分API甚至在意义上完全不同。但Promise的核心内容,是相通的,它就是then方法。在相关术语中,promise指的就是一个有then方法,且该方法能触发特定行为的对象或函数。

Promise可以有不同的实现方式,因此Promise核心说明并不会讨论任何具体的实现代码。

先阅读Promise核心说明的意思是:看,这就是需要写出来的结果,请参照这个结果想一想怎么用代码写出来吧。
起步:用这一种方式理解Promise

回想一下Promise解决的是什么问题?回调。例如,函数doMission1()代表第一件事情,现在,我们想要在这件事情完成后,再做下一件事情doMission2(),应该怎么做呢?

先看看我们常见的回调模式。doMission1()说:“你要这么做的话,就把doMission2()交给我,我在结束后帮你调用。”所以会是:

doMission1(doMission2);

Promise模式又是如何呢?你对doMission1()说:“不行,控制权要在我这里。你应该改变一下,你先返回一个特别的东西给我,然后我来用这个东西安排下一件事。”这个特别的东西就是Promise,这会变成这样:

doMission1().then(doMission2);

可以看出,Promise将回调模式的主从关系调换了一个位置(翻身做主人!),多个事件的流程关系,就可以这样集中到主干道上(而不是分散在各个事件函数之内)。

好了,如何做这样一个转换呢?从最简单的情况来吧,假定doMission1()的代码是:

function doMission1(callback){
  var value = 1;
  callback(value);
}

那么,它可以改变一下,变成这样:

function doMission1(){
  return {
    then: function(callback){
      var value = 1;
      callback(value);
    }
  };
}

这就完成了转换。虽然并不是实际有用的转换,但到这里,其实已经触及了Promise最为重要的实现要点,即Promise将返回值转换为带then方法的对象。
进阶:Q的设计路程
从defer开始

design/q0.js是Q初步成型的第一步。它创建了一个名为defer的工具函数,用于创建Promise:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      value = _value;
      for (var i = 0, ii = pending.length; i < ii; i++) {
        var callback = pending[i];
        callback(value);
      }
      pending = undefined;
    },
    then: function (callback) {
      if (pending) {
        pending.push(callback);
      } else {
        callback(value);
      }
    }
  }
};

这段源码可以看出,运行defer()将得到一个对象,该对象包含resolve和then两个方法。请回想一下jQuery的Deferred(同样有resolve和then),这两个方法将会是近似的效果。then会参考pending的状态,如果是等待状态则将回调保存(push),否则立即调用回调。resolve则将肯定这个Promise,更新值的同时运行完所有保存的回调。defer的使用示例如下:

var oneOneSecondLater = function () {
  var result = defer();
  setTimeout(function () {
    result.resolve(1);
  }, 1000);
  return result;
};

oneOneSecondLater().then(callback);

这里oneOneSecondLater()包含异步内容(setTimeout),但这里让它立即返回了一个defer()生成的对象,然后将对象的resolve方法放在异步结束的位置调用(并附带上值,或者说结果)。

到此,以上代码存在一个问题:resolve可以被执行多次。因此,resolve中应该加入对状态的判断,保证resolve只有一次有效。这就是Q下一步的design/q1.js(仅差异部分):

resolve: function (_value) {
  if (pending) {
    value = _value;
    for (var i = 0, ii = pending.length; i < ii; i++) {
      var callback = pending[i];
      callback(value);
    }
    pending = undefined;
  } else {
    throw new Error("A promise can only be resolved once.");
  }
}

对第二次及更多的调用,可以这样抛出一个错误,也可以直接忽略掉。
分离defer和promise

在前面的实现中,defer生成的对象同时拥有then方法和resolve方法。按照定义,promise关心的是then方法,至于触发promise改变状态的resolve,是另一回事。所以,Q接下来将拥有then方法的promise,和拥有resolve的defer分离开来,各自独立使用。这样就好像划清了各自的职责,各自只留一定的权限,这会使代码逻辑更明晰,易于调整。请看design/q3.js:(q2在此跳过)

var isPromise = function (value) {
  return value && typeof value.then === "function";
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = _value;
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          callback(value);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (callback) {
        if (pending) {
          pending.push(callback);
        } else {
          callback(value);
        }
      }
    }
  };
};

如果你仔细对比一下q1,你会发现区别很小。一方面,不再抛出错误(改为直接忽略第二次及更多的resolve),另一方面,将then方法移动到一个名为promise的对象内。到这里,运行defer()得到的对象(就称为defer吧),将拥有resolve方法,和一个promise属性指向另一个对象。这另一个对象就是仅有then方法的promise。这就完成了分离。

前面还有一个isPromise()函数,它通过是否有then方法来判断对象是否是promise(duck-typing的判断方法)。为了正确使用和处理分离开的promise,会像这样需要将promise和其他值区分开来。
实现promise的级联

接下来会是相当重要的一步。到前面到q3为止,所实现的promise都是不能级联的。但你所熟知的promise应该支持这样的语法:

promise.then(step1).then(step2);

以上过程可以理解为,promise将可以创造新的promise,且取自旧的promise的值(前面代码中的value)。要实现then的级联,需要做到一些事情:

  •     then方法必须返回promise。
  •     这个返回的promise必须用传递给then方法的回调运行后的返回结果,来设置自己的值。
  •     传递给then方法的回调,必须返回一个promise或值。

design/q4.js中,为了实现这一点,新增了一个工具函数ref:

var ref = function (value) {
  if (value && typeof value.then === "function")
    return value;
  return {
    then: function (callback) {
      return ref(callback(value));
    }
  };
};

这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子:

ref("step1").then(function(value){
  console.log(value); // "step1"
  return 15;
}).then(function(value){
  console.log(value); // 15
});

你可以看到value是怎样传递的,promise级联需要做到的也是如此。

design/q4.js通过结合使用这个ref函数,将原来的defer转变为可级联的形式:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value); // values wrapped in a promise
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          value.then(callback); // then called instead
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback) {
        var result = defer();
        // callback is wrapped so that its return
        // value is captured and used to resolve the promise
        // that "then" returns
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        if (pending) {
          pending.push(callback);
        } else {
          value.then(callback);
        }
        return result.promise;
      }
    }
  };
};

原来callback(value)的形式,都修改为value.then(callback)。这个修改后效果其实和原来相同,只是因为value变成了promise包装的类型,会需要这样调用。

then方法有了较多变动,会先新生成一个defer,并在结尾处返回这个defer的promise。请注意,callback不再是直接取用传递给then的那个,而是在此基础之上增加一层,并把新生成的defer的resolve方法放置在此。此处可以理解为,then方法将返回一个新生成的promise,因此需要把promise的resolve也预留好,在旧的promise的resolve运行后,新的promise的resolve也会随之运行。这样才能像管道一样,让事件按照then连接的内容,一层一层传递下去。
加入错误处理

promise的then方法应该可以包含两个参数,分别是肯定和否定状态的处理函数(onFulfilled与onRejected)。前面我们实现的promise还只能转变为肯定状态,所以,接下来应该加入否定状态部分。

请注意,promise的then方法的两个参数,都是可选参数。design/q6.js(q5也跳过)加入了工具函数reject来帮助实现promise的否定状态:

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      return ref(errback(reason));
    }
  };
};

它和ref的主要区别是,它返回的对象的then方法,只会取第二个参数的errback来运行。design/q6.js的其余部分是:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value);
        for (var i = 0, ii = pending.length; i < ii; i++) {
          value.then.apply(value, pending[i]);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback, _errback) {
        var result = defer();
        // provide default callbacks and errbacks
        _callback = _callback || function (value) {
          // by default, forward fulfillment
          return value;
        };
        _errback = _errback || function (reason) {
          // by default, forward rejection
          return reject(reason);
        };
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        var errback = function (reason) {
          result.resolve(_errback(reason));
        };
        if (pending) {
          pending.push([callback, errback]);
        } else {
          value.then(callback, errback);
        }
        return result.promise;
      }
    }
  };
};

这里的主要改动是,将数组pending只保存单个回调的形式,改为同时保存肯定和否定的两种回调的形式。而且,在then中定义了默认的肯定和否定回调,使得then方法满足了promise的2个可选参数的要求。

你也许注意到defer中还是只有一个resolve方法,而没有类似jQuery的reject。那么,错误处理要如何触发呢?请看这个例子:

var defer1 = defer(),
promise1 = defer1.promise;
promise1.then(function(value){
  console.log("1: value = ", value);
  return reject("error happens"); 
}).then(function(value){
  console.log("2: value = ", value);
}).then(null, function(reason){
  console.log("3: reason = ", reason);
});
defer1.resolve(10);

// Result:
// 1: value = 10
// 3: reason = error happens

可以看出,每一个传递给then方法的返回值是很重要的,它将决定下一个then方法的调用结果。而如果像上面这样返回工具函数reject生成的对象,就会触发错误处理。
融入异步

终于到了最后的design/q7.js。直到前面的q6,还存在一个问题,就是then方法运行的时候,可能是同步的,也可能是异步的,这取决于传递给then的函数(例如直接返回一个值,就是同步,返回一个其他的promise,就可以是异步)。这种不确定性可能带来潜在的问题。因此,Q的后面这一步,是确保将所有then转变为异步。

design/q7.js定义了另一个工具函数enqueue:

var enqueue = function (callback) {
  //process.nextTick(callback); // NodeJS
  setTimeout(callback, 1); // Na&#63;ve browser solution
};

显然,这个工具函数会将任意函数推迟到下一个事件队列运行。

design/q7.js其他的修改点是(只显示修改部分):

var ref = function (value) {
  // ...
  return {
    then: function (callback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(callback(value));
      });
      return result.promise;
    }
  };
};

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(errback(reason));
      });
      return result.promise;
    }
  };
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      // ...
          enqueue(function () {
            value.then.apply(value, pending[i]);
          });
      // ...
    },
    promise: {
      then: function (_callback, _errback) {
          // ...
          enqueue(function () {
            value.then(callback, errback);
          });
          // ...
      }
    }
  };
};

即把原来的value.then的部分,都转变为异步。

到此,Q提供的Promise设计原理q0~q7,全部结束。
结语

即便本文已经是这么长的篇幅,但所讲述的也只到基础的Promise。大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()、spread(),不过,读到这里,你已经了解了实现Promise的核心理念,这一定对你今后应用Promise有所帮助。

在我看来,Promise是精巧的设计,我花了相当一些时间才差不多理解它。Q作为一个典型Promise库,在思路上走得很明确。可以感受到,再复杂的库也是先从基本的要点开始的,如果我们自己要做类似的事,也应该保持这样的心态一点一点进步。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。