草庐IT

javascript - 基本的 Javascript promise 实现尝试

coder 2023-07-04 原文

为了更好地理解 promises 在 Javascript 中的工作原理,我决定尝试一下并自己编写基本实现代码。

基本上,我想实现将函数作为参数的 Promises 对象(我在代码中将其称为 Aaa)。此函数可以调用 resolve 来resolve promise,或者 reject 来reject 它。基本实现和使用如下。根据 promise 规范,不确定第二个参数是否可以接受,但这就是我目前所得到的。

Aaa=function(f,pause) { 

    console.log("ggg");

    var t=this;
    this.f=f;
    this.thens=[];

    this.resolve=function(g) {

        for(var i=0;i<t.thens.length;i++)
        {
            // try/catch to be used later for dealing with exceptions

            try
            {
                t.thens[i].f(g);
                t.thens[i].resolve();
            }   
            catch(ex)
            {}

        }
    };  

    // to be implemented later
    this.reject=function(g) {};

    this.then=function(resolve,reject) {

        // i'm passing true for pause argument as we dont need to execute promise code just yet
        var nextPromise=new Aaa(resolve,true);

        this.thens.push(nextPromise);

        return nextPromise;
    }


    if(!pause)
        this.f(this.resolve,this.reject); 

}


var aaa=new Aaa(function(resolve,reject) {

    console.log("aaa");

    setTimeout(function() {

        console.log("fff");
        resolve("good");

    },2000);

    console.log("bbb");

});

现在可以创建、调用和解析 promise。每个 then 方法都将返回新的 Aaa(Promise),因此可以将它们链接起来。现在下面的代码使用上面创建的 promise 并链接 then 回调。每个 then 都会返回新的 promise ,在这种情况下它似乎工作正常:

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

我得到的输出是:

ggg
aaa 
bbb 
ggg 
ggg 
fff 
ccc 
good 
ddd 
undefined 

然而,问题是当其中一个 then 调用返回一个 promise 时:

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

    // here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that?

        return new Aaa(function(resolve,reject) {

        console.log("iii");

        setTimeout(function() {
        console.log("kkk");
            resolve("good2");
            // reject("bad");

        },2000);

        console.log("jjj");

    }).then(function (res) {
        console.log("lll");

        console.log(res);
    });

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

输出是:

ggg 
aaa 
bbb 
ggg 
ggg  
fff  
ccc  
good  
ggg  
iii  
jjj  
ggg  
ddd  
undefined  
kkk  
lll  
good2 

输出 ddd 的调用不应该被调用,直到我们刚刚添加的返回的 promise 被解析。

如何最好地实现?

最佳答案

这里有许多您没有处理的案例。最好的办法是从将 promise 构建为状态机开始:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];
}

现在让我们定义一个简单的帮助程序,以在我们的其余实现中使用:

// a function that returns `then` if `value` is a promise, otherwise `null`
function getThen(value) {
  if (value && (typeof value === 'object' || typeof value === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

接下来,我们需要考虑可能发生的每个转换:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }
}

注意 resolve 如何接收一个 Promise 作为它的参数,但是一个 Promise 永远不能被另一个 Promise 实现。所以我们必须处理这种特殊情况。

另请注意,Promise 只能被履行/拒绝一次。我们也有第三方 Promise 可能行为不当的问题,我们应该保护我们的代码免受这种情况的影响。出于这个原因,我不仅在 resolve 中调用了 result.then(resolve, reject)。相反,我将其拆分为一个单独的函数:

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

所以现在我们有一个完整的状态机,但是没有办法观察或触发状态的变化。让我们首先添加一种通过传入解析器函数来触发状态更改的方法。

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  doResolve(fn, resolve, reject);
}

如您所见,我们重新使用了 doResolve,因为我们有另一个不受信任的解析器。 fn 可能会多次调用 resolvereject,并且可能会抛出错误。我们需要处理所有这些情况(这就是 doResolve 所做的)。

我们现在已经完成了状态机,但是我们还没有公开任何关于它处于什么状态的信息。让我们尝试添加一个 .done(onFulfilled, onRejected) 方法,就像 .then 除了它不返回 Promise 并且不处理 onFulfilledonRejected 抛出的错误。

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
      handlers.forEach(handle);
      handlers = null;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED && typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED && typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }
  this.done = function (onFulfilled, onRejected) {
    setTimeout(function () { // ensure we are always asynchronous
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}

请注意我们必须如何处理 .done 在 Promise 实现/拒绝之前和之后被调用的情况。

我们几乎有一个完整的 promise 实现,但是,正如您在构建自己的实现时已经注意到的那样,我们需要一个返回 Promise 的 .then 方法。

我们可以用 .done 轻松构建它:

this.then = function (onFulfilled, onRejected) {
  var self = this;
  return new Promise(function (resolve, reject) {
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}

请注意,我们现在是如何免费获得您苦苦挣扎的事情的,因为 resolve 接受一个 Promise 并等待它被解决。

N.B. 我没有测试过这个 Promise 实现(尽管据我所知它是正确的)。您应该根据 Promises/A+ 测试套件 (https://github.com/promises-aplus/promises-tests) 测试您构建的任何实现,并且还可能会发现 Promises/A+ 规范 (https://github.com/promises-aplus/promises-spec) 有助于确定算法的任何特定部分的正确行为是什么。作为最终资源,promise是 Promise 规范的最小实现。

关于javascript - 基本的 Javascript promise 实现尝试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23772801/

有关javascript - 基本的 Javascript promise 实现尝试的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  4. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

随机推荐