手写一个promise用法_手写一个自己的 JavaScript Promise 类库

本文介绍如何手写一个JavaScript Promise类库,从简单的实现到支持链式调用、异常处理和异步执行,遵循Promises/A+规范。通过逐步改进,解释Promise的工作原理和关键功能。
摘要由CSDN通过智能技术生成

终于有时间写这篇文章了, ES2015 推出了JS 的 Promise ,而在没有原生支持的时候,我们也可以使用诸如 Promises/A+ 的库的帮助,在我们的代码里实现Promise 的支持;

如何使用 Promise

在讲具体实现之前我们还是先了解下我们如何使用 Promise 在我们的代码中。

function getData() {

return new Promise((resolve, reject)=>{

request( your_url, (error, res, movieData)=>{

if (error) reject(error);

else resolve(movieData);

});

});

}

// 使用 getData

getData().then(data => console.log(data))

.catch(error => console.log(error));

例子我们需要使用 request 模块去请求一个地址,然后拿到响应的数据。由于 request 过程是一个异步的过程。因此我们使用了 promise 来实现。在使用 promsie 的 then 方法拿到 resolve 回来的的数据。

开始基本实现

首先我们定义一个基本的 Promise 类,为了区别于原生的 Promise 我们可以自定义一个 JPromise 类。

function Promise(fn) {

var callback = null;

this.then = function(cb) {

callback = cb;

};

function resolve(value) {

callback(value);

}

fn(resolve);

}

我们这个时候先添加一些简单的测试用例。

/** test a single plugin */

import { expect } from 'chai';

import JPromise from '../../src/index';

describe('your own promise library unit tests', () => {

let data = 1;

const p = new JPromise((resolve) => {

resolve(5);

});

it('has then method', () => {

expect(typeof p.then === 'function').to.equal(true);

});

it('excute a then function', () => {

p.then((val) => data = val);

expect(data).to.equal(5);

});

});

但是这样写法有一个问题就是你会发现他在 resolve() 会在 then 之前调用。意味着 callback 会是 null .暂时我们用 setTimeout 来 hack 这个问题。

function Promise(fn) {

var callback = null;

this.then = function(cb) {

callback = cb;

};

function resolve(value) {

setTimeout(function() {

callback(value);

}, 1);

}

fn(resolve);

}

但实际我们在测试的过程中,会发现这样只是规避 resolve 先执行的问题,后面会继续说如何解决上面的问题。

再进一步,我们需要 Promise 有状态。

Promise 的状态可以是 pending 等待一个值,也可以是 resolved 返回一个值

一旦 Promise 返回一个值,则后面将不再返回

function JPromise(fn) {

let state = 'pending';

let deferred = null;

let value = null;

function resolve(newVal) {

value = newVal;

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);

}

module.exports = JPromise;

通过状态,我们可以确保每次函数触发 then 方法的时候, resolve 始终都能够被调用到。

似乎这个时候我们的测试可以跑通了。但是,远远没有结束,我们要确保 Promise 的链式调用。

getSomeData()

.then(filterTheData)

.then(processTheData)

.then(displayTheData);

我们可能经常会看到这样的写法。很显然刚刚的不合适了。函数会一次执行 then 方法,然后等待 传入 resolve 里的函数执行完毕,然后执行下一个 then 方法 。

如果熟悉 jQuery 链式调用 的话,可以很好的理解。

我们会在 then 的方法里面返回 Promise 对象。

function JPromise(fn) {

let state = 'pending';

let deferred = null;

let value = null;

function resolve(newVal) {

value = newVal;

state = 'resolved';

if (deferred) {

handle(deferred);

}

}

function handle(handler) {

if (!handler.onResolved) {

handler.resolve(value);

return;

}

const ret = handler.onResolved(value);

handler.resolve(ret);

}

this.then = function(onResolved) {

return new Promise((resolve) => {

handle({

onResolved,

resolve,

});

});

};

fn(resolve);

}

module.exports = JPromise;

从上面代码我们中可以看出,我们在调用 then 的方法适合,始终都会创建一个 Promise 对象。而使用回调的时候则可以避免这样一个问题。

那么第二个 Promise resolve 传入的实际上是第一个 Promise 返回的值。

const ret = handler.onResolved(value);

我们会将函数执行的值,带入下一个 resolve 中。由于 then() 方法始终都是返回一个 Promsie

当然 then() 方法也允许你不传入 callback 进去,比如下面这样:

doSomething().then().then(function(result) {

console.log('got a result', result);

});

当然这些就是程序兼容性的处理。我们需要在没有传入的时候将之前保留的函数进行触发

if(!handler.onResolved) {

handler.resolve(value);

return;

}

对 reject 进行支持

如果遇到问题了,我们需要对这些进行抛出,外部能够获取到这些问题。

doSomething().then(function(value) {

console.log('Success!', value);

}, function(error) {

console.log('Uh oh', error);

});

如同上面实现的一样我们需要将内部的 state 置位 rejected。

function doSomething() {

return new Promise(function(resolve, reject) {

var result = somehowGetTheValue();

if(result.error) {

reject(result.error);

} else {

resolve(result.value);

}

});

}

参考 resolve 的实现,

function JPromise(fn) {

let state = 'pending';

let deferred = null;

let value = null;

function resolve(newVal) {

value = newVal;

state = 'resolved';

if (deferred) {

handle(deferred);

}

}

function reject(reason) {

state = 'rejected';

value = reason;

if (deferred) {

handle(deferred);

}

}

function handle(handler) {

if (state === 'pending') {

deferred = handler;

return;

}

let handlerCallback;

if (state === 'resolved') {

handlerCallback = handler.onResolved;

} else {

handlerCallback = handler.onRejected;

}

if (!handlerCallback) {

if (state === 'resolved') {

handler.resolve(value);

} else {

handler.reject(value);

}

return;

}

const ret = handlerCallback(value);

handler.resolve(ret);

}

this.then = function(onResolved, onRejected) {

return new Promise((resolve, rejected) => {

handle({

onResolved,

onRejected,

resolve,

rejected

});

});

};

fn(resolve, reject);

}

module.exports = JPromise;

实现到这里的时候,我们还需要对异常的错误进行处理;在 Promise 用 catch 进行捕获。

function resolve(newValue) {

try {

// ... as before

} catch(e) {

reject(e);

}

}

除此之外,我们还得防止 在执行回调的时候,在被调用的过程中出现异常。

function handle(deferred) {

// ... as before

let ret;

try {

ret = handlerCallback(value);

} catch(e) {

handler.reject(e);

return;

}

handler.resolve(ret);

}

似乎感觉实现的差不多了。但是刚才的实现有一个不好的地方,就是 Promise 中用了 try…catch… 。这样 Promise 会吞噬错误。也就是:

function getSomeJson() {

return new Promise(function(resolve, reject) {

var badJson = "

uh oh, this is not JSON at all!"; resolve(badJson); }); } getSomeJson().then(function(json) { var obj = JSON.parse(json); console.log(obj); }, function(error) { console.log('uh oh', error); });

这个 JSON 解析的时候会出现错误,但实际上我们在控制台看不到这样的错误。如果你想捕获这些错误,你需要添加一个错误处理的函数:

getSomeJson().then(function(json) {

var obj = JSON.parse(json);

console.log(obj);

}).then(null, function(error) {

console.log("an error occured: ", error);

});

Promise 需要异步处理

前面我们提到了使用 setTimeout 去故意延迟执行,随后我们通过 state 去克服了这么一个问题,但是 Promises/A+ 的定义额 promise 的解决方案的确是异步执行的。这样我们可以利用 setTimeout 去包裹 handle 里的 resovle 执行;

function handle(handler) {

if(state === 'pending') {

deferred = handler;

return;

}

setTimeout(function() {

// ... as before

}, 1);

}

实际上,在实现过程中,我们可以使用 setImmediate 而不是 setTimeout。

var promise = doAnOperation();

invokeSomething();

promise.then(wrapItAllUp);

invokeSomethingElse();

上面的代码,我们预期的效果执行流程

doAnOperation -> invokeSomething -> invokeSomethingElse -> wrapItAllUp

然而如果 doAnOperation 并非异步执行的函数的话,可能实际效果是:

doAnOperation -> invokeSomething -> wrapItAllUp -> invokeSomethingElse

这就是为什么 promise 始终都会去异步执行传入的回调。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值