一个Promise库的实现步骤(Promise原理)

本文出处(附所有demo):https://github.com/bellemere/Promise为什么我们需要Promise涉及到异步编程的时候,js经常会遇到回调地狱,例如(test1.js):function load(url, cb){ setTimeout(function(){ console.log(url); cb() }, 1000);}l...
摘要由CSDN通过智能技术生成

本文出处(附所有demo):
https://github.com/bellemere/Promise

为什么我们需要Promise

涉及到异步编程的时候,js经常会遇到回调地狱,例如(test1.js):

function load(url, cb){
   
  setTimeout(function(){
   
    console.log(url);
    cb()
  }, 1000);
}

load('a.com', function(){
   
  load('b.com', function(){
   
    load('c.com', function(){
   
      load('d.com', function(){
   
        load('e.com', function(){
   
          load('f.com', function(){
   
            load('g.com', function(){
   
              //...
            })	
          })
        })
      })
    })
  })
})

上面的嵌套里面往往还含有别的逻辑,所以嵌套会更加恐怖。

而Promise则是如下处理(test2.js):

function load2(url){
   
  return new Promise(function(resolve){
   
    setTimeout(function(){
   
      console.log(url);
      resolve();
    }, 1000)
  })
}

load2('a.com')
.then(function(){
   
  return load2('b.com')
})
.then(function(){
   
  return load2('c.com')
})
.then(function(){
   
  return load2('d.com')
})
.then(function(){
   
  return load2('e.com')
})
.then(function(){
   
  return load2('f.com')
})
.then(function(){
   
  return load2('g.com')
})

从代码阅读体验来说,会好很多。


Promise是如何一步步实现的呢?

最近看到了promise库Q的作者的一篇文章,讲述了从0实现Q的大概步骤及原理。

原文出处

https://github.com/kriskowal/q/tree/v1/design

一些名词

pending:promise起始状态

resolved:异步操作取得了结果(不管成功还是失败)

fulfilled:resolved成功的状态(ES6里面的resolve是进入这种状态)

rejected: resolved失败的状态

前言

假设我们写的某个函数并不是马上返回一个值,那如果我们需要对这个值做一些操作,最直接的方式是给函数传入一个回调函数作为参数:

var oneOneSecondLater = function (callback) {
   
  setTimeout(function () {
   
    //假设value是我们异步操作最终取得的值
    var value = 1;
    callback(value);
  }, 1000);
};

通常我们还需要对错误的情况(比如网络通信失败/文件读写失败)也进行处理,所以除了正常的callback,我们往往还需要提供一个errback的回调函数:

var maybeOneOneSecondLater = function (callback, errback) {
   
  setTimeout(function () {
   
    if (Math.random() < .5) {
   
      callback(1);
    } else {
   
      errback(new Error("Can't provide one."));
    }
  }, 1000);
};

把error当成一个参数传递给回调函数有不同的方法,比如可以当成位置参数,或者通过一个哨兵值来区分。不过,这些没有一个实际地模型化抛出错误。try/catch以及错误的目的是把错误处理延迟到程序确实想要处理它的地方。如果他们没有被处理,必须有一些机制隐式地传递这些错误。

Promise

让我们考虑一种更为通用的方式。

原先我们的函数返回一个值或是抛出一个错误,现在我们返回一个对象,这个对象包含了这个函数的最终结果(成功或者失败)。这个对象就是一个promise,它最终一定会定型(也就是resolved)。我们可以在这个对象上调用一个方法来观察到它最终是成功(fulfilled)还是失败(rejected)了。如果promise对象rejected了,衍生的promise对象会因为相同的理由reject。

在上述的设计理念的迭代过程中,我们先把promise模型化成一个拥有then方法的对象,then方法用来注册我们的回调函数(test3.js):

var maybeOneOneSecondLater = function () {
   
  //保存回调函数
  var callback;
  setTimeout(function () {
   
    var value = 1;
    callback(value);
  }, 1000);
  return {
   
    then: function (_callback) {
   
      callback = _callback;
    }
  };
};

var m = maybeOneOneSecondLater();

m.then(()=>console.log(1));
m.then(()=>console.log(2));
m.then(()=>console.log(3));
//3

setTimeout(function(){
   
  m.then(()=>console.log(4));
}, 3000);
//不执行

如上,这个设计有2个缺点:

  • 最后一次的then调用决定了使用的回调函数,而我们希望注册的每个回调函数在收到结果的时候都能被通知到。
  • 如果回调函数在promise对象构建后的一秒后注册,就不会被调用了。

我们先看看ES6里面Promise针对上面两种情况的处理结果(test4.js):

var p = new Promise(function(resolve){
   
  setTimeout(function(){
   
    resolve()
  }, 1000)
});

//注册了多个回调函数
p
.then(()=>console.log(1))
.then(()=>console.log(2))
.then(()=>console.log(3))
//1
//2
//3

//在Promise对象构建后再注册回调函数
setTimeout(function(){
   
  p.then(()=>console.log(4))
}, 3000);
//4

我们希望promise对象能够注册任意数量的回调函数,并且不管在超时前或者已经超时都能够注册。我们可以通过让promise拥有2个状态来实现上述需求。

promise开始的时候是未定型的(pending),这时候所有的回调函数都添加到一个观察数组里面。当promise定型了(resolved),数组里的所有回调函数都会被调用。在promise定型后,新注册的回调函数会马上执行。我们通过观察数组是否存在来区分promise的状态(pending/resolved)。

修改代码如下(test5.js):

var maybeOneOneSecondLater = function () {
   
  //pending存在表示unresolved, 不存在则表示resolved
  var pending = [], value;
  setTimeout(function () {
   
    value = 1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值