Promise 初步接触

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Promise 初步接触相关的知识,希望对你有一定的参考价值。

最近看了《javascript Promise迷离书》,对Promise的理解颇有加深。那么就从总结Promise开始吧。

 1 什么是Promise?

  抽象描述: Promise是一个规范,提供了一套定义用来与 一个可能会在任意时刻完成或失败的异步过程的结果对象交互的接口。( Promise提供了一套接口,用来与异步过程的结果的对象 交互。这样读着好(bing)理(mei)解(you)点)

  简单描述:Promise是一个异步对象,在Promise对象创建时可能是未知的,当状态变换后Promise表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,它允许你为异步代码执行结果的成功和失败分别绑定相应的处理方法(handlers ),用于处理resolve的value(常用变量名)或reject 的reason(常用变量名)。 

// 在Promise对象创建时可能是未知的,
var promise = new Promise(function(resolve,reject){
  if (Math.random() > 0.5)
    // 状态变换~~Promise表示一个异步操作的最终结果 成功
    resolve(\'value resolve\');
  else {
    // 状态变换~~Promise表示一个异步操作的最终结果 失败
    reject(\'reason reject\');
  }
});
// 与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数
promise.then(function(value){
  // 处理 异步代码执行成功的结果
    console.log(value);
}).catch(function(error){
  // 处理 异步代码执行失败的结果
    console.error(error);
});

 1.1 Promise对象的创建

new Promise构造器之后,会返回一个promise对象,创建Promise对象的基本语法: 

// new Promise(executor);
// 给Promise构造函数传递的参数是一个函数
// 函数接受两个参数resolve, reject,

new Promise(function(resolve, reject) {
    // 异步处理的到结果后、判定什么情况下resolve这个结果,什么情况下reject这个结果.
    // 通常resolve(value),会将promise的状态转变成Fulfilled
    // 通常reject(reason),会将promise的状态转变为Rejected
    if ( /* 异步操作成功的判定条件 */ ) {
        resolve(value);
    } else {
        reject(reason);
    }
});

new一个Promise对象,给Promise构造函数传递的参数是一个函数Fn(Promise构造函数只接受函数作为参数)。该函数Fn接受两个参数resolve和 reject。

那么Promise 内部实现中会给这个函数Fn传递的参数具体是两个什么方法了?

代码参考es6-promise

function Promise(resolver) {
  this[PROMISE_ID] = nextId();
  this._result = this._state = undefined;
  this._subscribers = [];

  if (noop !== resolver) {
    typeof resolver !== \'function\' && needsResolver();
    this instanceof Promise ? initializePromise(this, resolver) : needsNew();
  }
}

function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value) {
      _resolve(promise, value);
    }, function rejectPromise(reason) {
      _reject(promise, reason);
    });
  } catch (e) {
    _reject(promise, e);
  }
}

 

比对着图看更清晰

 

那么内部执行resolve和执行reject主要做了什么事情了?先从表象推结果

如下图创建的testResolvePromise,在Promise构造函数接受的函数内部执行了 resolve。

testResolvePromise对象的[[PromiseStatus]]值是"resolved",[[PromiseValue]]值"resolve: value"(这个字符串就是我们传入resolve函数的值)。

对比testRrejectPromise,testRrejectPromise对象的[[PromiseStatus]]值是"rejected",[[PromiseValue]]值"reject:reason"(这个字符串就是我们传入reject函数的值)。

(1)resolve和reject的调用改变了promise的状态。

(2)同时将处理后的值赋给了promise对象的[[PromiseValue]]属性(为什么是处理后,见后文。如果你resolve的value值是一个promise对象,那么就不是简单的赋值了。

 总结:给Promise构造函数传递的参数是一个函数,且该函数接受的两个参数也是函数,由Promise的内部实现,用以改变创建的promise对象的状态。

 1.2 Promise的状态

用new Promise 实例化的promise对象有三个状态: pending,fulfilled,rejected。

pending: promise对象刚被创建的初始状态,既未完成也没有失败的状态,此状态可以迁移至fulfilled和rejected状态。

fulfilled:意味着操作成功完成,resolve(成功)时,此时的状态不能迁移(不能改变的)。

rejected:意味着操作失败reject(失败)时,此时的状态不能迁移(不能改变的)。

eg: 如图创建了一个testPromisePending对象,用setTimeout设置一个时间10秒后再执行resolve。在这时间段前还没有执行resolve,此时的testPromisePending的[[PromiseStatus]]值是"pending"。10秒过后再在控制太输出一次testPromisePending,此时它的状态就迁移了[[PromiseStatus]]值是"resolved",即是fulfilled状态。

“promise对象的状态,从Pending转换为FulfilledRejected之后, 这个promise对象的状态就不会再发生任何变化”,--《javascript Promise迷离书》(书中的流程图十分的清晰,有利于promise的工作流程理解)

  pending状态---->resolve(value)----->fulfilled状态.

  pending状态---->reject(reason)----->rejected状态.

 总结:如果一个promise不是pending状态,就说明这个promise是settled(不变的),它要么fulfilled状态要么是rejected状态。

 1.3 Promise的then方法

  Promise构造函数接受一个函数作为参数,函数里面内部代码执行了resolve或reject。resolve(value)或 reject(reason)传递了值后续怎么处理?这时候then方法闪亮登场了,promise的then方法里面可以处理resolve(value) 或reject(reason)时得到的值。then方法接受两个可选参数。

var testPromise = new Promise(function(resolve, reject) {
    if ( /* 异步操作成功的判定条件 */ ) {
        resolve(value);
        //函数内部resolve了value值,那么我们怎么处理value值了;
    } else {
        reject(error);
        //函数内部reject了reason值,那么我们怎么处理reason值了;
    }
});
testPromise.then(function onFulfilled(value) {
    //当testPromise的状态是fulfilled的时候执行
}, function onRejected(reson) {
    //当testPromise的状态是rejected的时候执行
})

pending状态---->resolve(value)----->fulfilled状态---->执行then方法里面onFulfilled(value)方法

pending状态---->reject(reason)----->rejected状态---->执行then方法里面onRejected(reason)方法

  当testPromise的状态迁移成 fulfilled或rejected的时候时才执行后续的then方法。(两条路线只会执行一条,)

      testPromise的状态是fulfilled就执行onFulfilled方法,此时的value参数的值就是之前resolve的值,onFulfilled函数内部就对传递进来value值进行后续的处理了

      testPromise的状态是rejected就执行onRejected方法,此时的reason参数的值就是之前reason的值,onRejected函数内部就对传递进来reason值进行后续的处理。 

then方法接受两个可选参数,具体又干了什么了?

代码参考es6-promise 

假设目前代码逻辑是

// 在Promise对象创建时可能是未知的,
var promise = new Promise(function(resolve,reject) {
  setTimeout(function() {
    resolve(Math.random());
  })
});
var bPromise = promise.then(function(value) {
  console.log(value);
})

then执行完后返回的结果还是一个promise对象,这个新创建的promisethen方法执行的回调onFulfilled或者onRejected有关系(废话)。  

  ⑴.调用then方法会返回的promise是新创建的

  ⑵.这个新创建promise的值跟前一个promise的onFulfilled和onRejected的函数内部有无return 以及return的值有关系。如果没有return则返回一个状态为fulfilled的或者rejected,[[PromiseValue]](不同实现内部属性不一定叫PromiseValue)为undefined的promise对象。

  ⑶.承接⑵如果有return,retuen 一个普通的object对象那么新创建promise对象的 [[PromiseValue]] 的值就等于 object。 如果 return 的是一个Promise对象,还是会返回一个新的promise对象,且属性值[[PromiseValue]]和[[PromiseStatus]]与return 的Promise对象属性值一样

  ⑷.如果有return,retuen 一个普通的thenable对象,会先执行thenable对象的then方法,得到[[PromiseValue]]和[[PromiseStatus]],然后将值赋给新的promise对象(下一篇解释)

 

  返回了一个普通的promise,imANewPromiseB长什么样子

var testPromiseA = new Promise(function(resolve, reject) {
    resolve("testPromiseA")
});
var testForreturnPromise = new Promise(function(resolve, reject) {
    resolve("just test for testPromiseB")
});
//返回了一个普通的promise,imANewPromiseB长什么样子
var imANewPromiseB = testPromiseA.then(function onFulfilled(value) {
    //返回了一个promise
    return testForreturnPromise
})
// 在控制台输出,imANewPromiseB和 testForreturnPromise 的属性值一样,但他们不是同一个promise。
//imANewPromiseB 是一个新的promise 对象

  在控制台输出,imANewPromiseB和 testForreturnPromise 的属性值一样,但他们不是同一个promise,且imANewPromiseB和testPromiseA也不是同一个promise 对象。imANewPromiseB是一个新的promise。

总结:每次调用then都会返回一个新创建的promise对象。

 1.4 为啥要用Promise?

  有一个作用就是把代码从异步回调函数的逻辑混乱拯救出来(虽然还是要用回调但是已经比之前好了,期待async/await用起来更顺畅),让代码看起来逻辑清晰可爱。因为Promise把异步处理对象和处理规则进行规范化。

function getJSON(url) {
    return new Promise(function(resolve, reject) {
        let xhr = new XMLHttpRequest(); //神奇的对象
        xhr.open(\'GET\', url);
        xhr.onreadystatechange = handler;
        // 无论readyState值何时发生改变,XMLHttpRequest对象都会激发一个readystatechange事件,handler被调用,然后根据结果resolve,或者reject
        xhr.responseType = \'json\';
        xhr.setRequestHeader(\'Accept\', \'application/json\');
        xhr.send();
        function handler() {
            if (this.readyState === this.DONE) {
                if (this.status === 200) {
                    resolve(this.response);
                    //successDo(this.response)
                } else {
                    reject(new Error(\'getJSON: `\' + url + \'` failed with status: [\' + this.status + \']\'));
                    //faileDo(this.response)
                }
            }
        };
    });
}
// 使用promise
getJSON(url).then(function onFulfilled(value) {
    //successDo
}, function onRejected(reson) {
    //faileDo
})
// 使用回调
getJSON(url, successDo, faileDo)

  使用回调的方式来做getJSON,拿数据和对拿到数据成功和失败都在一个函数里面操作处理。

  使用promise来做getJSON,getJSON只需要做好自己拿数据,且通过resolve和reject返回拿数据成功还是失败的逻辑,并不关心成功或失败的处理。后续的then方法会根据getJSON返回的promise的状态执行onFulfilled方法或者onRejected方法来处理,数据拿成功或者数据拿失败的逻辑。这个流程比较符合人类(我这种人类)的习惯,一步一步执行操作,拿数据返回成功或失败----->处理拿数据成功或失败。

  回到文章的抽象描述,Promise处理了异步操作的结果,并提供了规范的接口让你与之交互

总结:Promise 可以让异步处理对象更像一个流程操作。使用Promise 的时候要思考Promise 的适用场景。并不是说在异步处理的时候Promise永远都是最好的选择。

 总结

本章Promise的描述是按照:new一个promise对象,给Promise构造函数传递的参数是一个函数。函数内部执行resolve或reject使promise对象的状态从pending状态迁移到fulfilled状态或者rejected状态,状态迁移后就不会再改变。(后续会说明为什么promise对象需要状态)。promise对象状态迁移成 fulfilled或rejected的时候时才执行后续的then方法,状态是fulfilled就执行then里面的onFulfilled方法,状态是rejected就执行then里面onRejected方法。then执行完后返回的是一个新的promise对象(后续会说明为什么它会返回一个新的promise对象)。

文中例子比较粗糙,理解不准确之处,还请教正。

第一次修正于(2019/03/31)

以上是关于Promise 初步接触的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段12——JavaScript的Promise对象

Promise 初步

前端面试题之手写promise

初步了解 promise -01

Promise的初步认识

初步了解 promise-02 简单手写源码