一步一步了解Promise原理

 最近看到一篇有关promise的文章还不错,想着翻译过来看看,顺便也算是对promise作个总结。

好了,开门见山,跟着我的脚步一起来学习吧。

一、最简单的例子

1、先看例子1:

function doSomething(callback) {
  var value = 42;
  callback(value);
}

doSomething(function(value) {
  console.log('Got a value:' + value);
});复制代码

这是一个传入函数作为回调函数的函数,调用回调函数得到value值。

需要做些什么改变让它看起来像‘promise’呢?来看例子2

2、例子2:

function doSomething() {
  return {
    then: function(callback) {
      var value = 42;
      callback(value);
    }
  };
}
doSomething().then(function(value) {
  console.log('Got a value:' + value);
});复制代码

例2是在例1的基础上做修改:在函数体内直接返回一个对象,然后对象里面有then,其value为function。

使用上是在.then()里传入一个回调函数。并且得到then中的value(42)。这使用看起来和promise好像一样了。

promise有个重要的思想:Promises capture the notion of an eventual value into an object。

这句话的意思是promise捕捉最终值传入到一个对象中。

读到这里也许有些困惑,没关系,一起继续探索强大有趣的事情吧!!!(老外的语言就是这么直白简单hhhhhh...)

二、定义Promise类

1、现在开始,进入正题。定义一个简单的Promise类。

function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    callback(value);
  }

  fn(resolve);
}

function doSomething() {
  return new Promise(function(resolve) {
    var value = 42;
    resolve(value);
  });
}复制代码

来调用一下:

doSomething().then(function(res){
  console.log(res);
})

//来分析一下执行顺序:
// doSomething -> new Promise -> fn -> resolve -> callback复制代码

emm...执行到这里,报错了??  callback是null 

为什么呢? 因为callback是在then()方法中被赋值的,现在resolve()早于then()执行,callback还是null的状态。

那怎么办呢?

我们用setTimeout来解决这个问题。

function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    // force callback to be called in the next
    // iteration of the event loop, giving
    // callback a chance to be set by then()
    setTimeout(function() {
      callback(value);
    }, 1);
  }

  fn(resolve);
}复制代码

好了 现在我们再来分析下执行过程。

doSomething -> new Promise -> fn -> resolve -> then -> callback复制代码

这样是顺序能够正常运行了,但是我觉得这个代码糟糕透了

来下面的例子:

var promise = doSomething()

setTimeout(function() {
    promise.then(function(value) {
    	log("got a value", value);
 });
复制代码

如果我们是这样调用呢,是不是又要报错了!

callback和then()先后被放入异步执行队列中,根据先进先出原则,callback先执行,可此时callback is not a function but null.

我们的这个尝试以失败告终,接着来...

2、给Promise添加状态

  • A promise can be **pending** waiting for a value, or **resolved** with a value.
  • Once a promise resolves to a value, it will always remain at that value and never resolve again.

这两句话的意思是一个promise要么处于pending状态(等待结果),要么已经resolved(得到结果),promise一旦resolved得到结果,它就一直保持这个结果不会再执行resolve。

(promise的reject稍后讨论)

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(onResolved) {
    if(state === 'pending') {
      deferred = onResolved;
      return;
    }

    onResolved(value);
  }

  this.then = function(onResolved) {
    handle(onResolved);
  };

  fn(resolve);
}
function doSomething() {
    return new Promise(function(resolve) {
        var value = 42;
        resolve(value);
    });
}

function doSomethingElse() {
    return new Promise(function(resolve) {
        var value = 3;
        setTimeout(function(){
            resolve(value);
        },1);
    });
}
复制代码

我们来调用一下:

测试1:
doSomething().then(function(value) {
    log("got a value", value);
});

//分析一下过程:
doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved复制代码

测试2:
var promise = doSomething();
setTimeout(function(){
    promise.then(function(value){
      log("got a value", value);
    })
})
//过程分析
doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved复制代码

测试3:
var promiseElse = doSomethingElse();
promiseElse.then(function(value){
      log("got a value", value);
});
//过程分析
doSomethingElse -> Promise -> fn -> then -> handle(deferred缓存onResolved) 
 ->setTimeout异步队列开始执行,resolve执行 -> handle(deferred)复制代码

以上测试无论何时调用then()和resolve(),根据state状态的判断,可以收放自如地完成同步异步函数执行。

With promises, the order in which we work with them doesn't matter. We are free to call `then()` and `resolve()` whenever they suit our purposes. This is one of the powerful advantages of capturing the notion of eventual results into an object

显然,到这里我们已经实现了Promise强大的pending-->resolved功能。

接下来我们要干什么呢?

三、链式Promises

平时我们对Promise的链式调用使用得很多吧,即使没使用过链式调用那也一定看到过哦。

`then()` always returns a promise

根据这个原则,我们稍作修改了代码

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if(state === 'pending') {
      deferred = handler;
      return;
    }

    if(!handler.onResolved) {
      handler.resolve(value);
      return;
    }

    var ret = handler.onResolved(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved) {
    return new Promise(function(resolve) {
      handle({
        onResolved: onResolved,
        resolve: resolve
      });
    });
  };

  fn(resolve);
}复制代码

 这里我们注意到:then()返回以个新的Promise对象,这使得我们在实例化一个Promise对象之后调用then()时还可以接着调用then(),形成了链式调用。

下面我们来看下测试用例:

doSomething().then(function(result) {
  console.log('first result', result);
  return 88;
}).then(function(secondResult) {
  console.log('second result', secondResult);
});
// first result 42
// second result 88复制代码

1、参数可选

当然then()方法里面的参数是可选,我们也可能遇到这样情况:

doSomething().then().then(function(result) {
  console.log('got a result', result);
});
// got a result 42
//这里第一个then没有回调函数 但是第二个then还是能得到结果
//执行顺序:
doSomething -> Promise -> fn -> resolve -> then -> handle -> handler.resolve(42) -> then 
-> handle -> handler.onResolve(42) -> handler.resolve(undefined)复制代码

这里的第一个then没有传入回调函数,所以直接执行handler.resolve()将value保存以待第二个then获取。

2、在链中返回Promise

doSomething().then(function(result) {
  // doSomethingElse returns a promise
  return doSomethingElse(result);
}).then(function(finalResult) {
  console.log("the final result is", finalResult);
});复制代码

这里的finalResult按照预期的结果是返回了Promise实例,但是如果我们想要doSomethingElse中resolved值怎么办?

修改一下resolve函数

function resolve(newValue) {
  if(newValue && typeof newValue.then === 'function') {
    newValue.then(resolve);
    return;
  }
  state = 'resolved';
  value = newValue;

  if(deferred) {
    handle(deferred);
  }
}复制代码

我们保持resolve()中value值不是一个promise实例而是一个普通值,所以在value赋值之前再做一次.then的操作,直到得到value的值为一个普通值。

附上完整代码,执行一遍。

function Promise(fn) {
    var state = 'pending';
    var value;
    var deferred = null;

    function resolve(newValue) {
        if (newValue && typeof newValue.then === 'function') {
            newValue.then(resolve);
            return;
        }
        state = 'resolved';
        value = newValue;

        if (deferred) {
            handle(deferred);
        }
    }

    function handle(handler) {
        if (state === 'pending') {
            deferred = handler;
            return;
        }

        if (!handler.onResolved) {
            handler.resolve(value);
            return;
        }

        var ret = handler.onResolved(value);
        handler.resolve(ret);
    }

    this.then = function (onResolved) {
        return new Promise(function (resolve) {
            handle({
                onResolved: onResolved,
                resolve: resolve
            });
        });
    };

    fn(resolve);
}

function doSomething() {
    return new Promise(function (resolve) {
        var value = 42;
        resolve(value);
    });
}

function doSomethingElse(value) {
    return new Promise(function (resolve) {
        resolve("did something else with " + value);
    });
}

doSomething().then(function (firstResult) {
    log("first result:", firstResult);
    return doSomethingElse(firstResult);
}).then(function (secondResult) {
    log("second result:", secondResult);
});

//first result: 42//second result: did something else with 42
//执行顺序分析:
doSomething -> Promise -> fn -> resolve -> then -> Promise -> fn -> handle -> handler.onResolved
-> handler.resolve(doSomethingElse(firstResult)) -> newValue.then -> Promise -> fn -> handle(这里的onResolved传入的是上一个then的resolve)
-> handler.onResolved 这里完成对上一个then的value赋值 -> handler-resolve -> then(这里开始第二个then)复制代码

这里过程有点绕,不过慢慢分析可以理清楚这中间value传递。

四、reject处理

终于要加上reject部分的处理啦 其实和resolve部分差不多。当程序运行错误时,我们需要获知为什么产生了错误,那么在promise中我们知道错误的发生呢??

这时需要then的第二个回调函数!

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    if(newValue && typeof newValue.then === 'function') {
      newValue.then(resolve, reject);
      return;
    }
    state = 'resolved';
    value = newValue;

    if(deferred) {
      handle(deferred);
    }
  }

  function reject(reason) {
    state = 'rejected';
    value = reason;

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if(state === 'pending') {
      deferred = handler;
      return;
    }

    var handlerCallback;

    if(state === 'resolved') {
      handlerCallback = handler.onResolved;
    } else {
      handlerCallback = handler.onRejected;
    }

    if(!handlerCallback) {
      if(state === 'resolved') {
        handler.resolve(value);
      } else {
        handler.reject(value);
      }

      return;
    }

    var ret = handlerCallback(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved, onRejected) {
    return new Promise(function(resolve, reject) {
      handle({
        onResolved: onResolved,
        onRejected: onRejected,
        resolve: resolve,
        reject: reject
      });
    });
  };

  fn(resolve, reject);
}复制代码

我们来调用一下:

function doSomething() {
  return new Promise(function(resolve, reject) {
    var result = somehowGetTheValue();
    if(result.error) {
      reject(result.error);
    } else {
      resolve(result.value);
    }
  });
}复制代码


As mentioned earlier, the promise will transition from **pending** to either **resolved** or **rejected**, never both. In other words, only one of the above callbacks ever gets called

上面doSomething调用时,我们发现Promise的回调方法中,如果有错误发生,那么调用reject();如果没有错误发生,那么调用resolve()。Promise会从pending到resolved,或者从pending到rejected,只有这两种情况,而且只能发生其一,也就是说要么成功,要么失败。

意想不到的错误我们也要处理。

刚刚我们调用reject()方法是在我们能知道的情况下,但是万一在promise中发生错误我们应该怎么办呢?

这里有两个采取捕捉错误的方法。

1、在resolve中可能发生错误进行捕捉

function resolve(newValue) {
  try {
    // ... as before
  } catch(e) {
    reject(e);
  }
}复制代码

2、在handle方法中进行错误捕捉

function handle(deferred) {
  // ... as before

  var ret;
  try {
    ret = handlerCallback(value);
  } catch(e) {
    handler.reject(e);
    return;
  }

  handler.resolve(ret);
}复制代码

文章到这里,reject的部分也完成了。如果你对done()、all()、race()等方法感兴趣你可以去看promise的官方API。

github上有学习的代码:https://github.com/Turboemily/learn_promise_development.git

以上内容来自文章:https://www.mattgreer.org/articles/promises-in-wicked-detail/#defining-the-promise-type。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值