promise用法(1)

我们先了解学会promise用法,为手写promise做准备。[握拳小手]
参考文章:
廖雪峰的官网
使用 Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值,是一种承诺将来会执行”的对象。

前言

在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

function callback() {
  console.log("Done。。");
}
console.log("before setTimeout()..");
setTimeout(callback, 1000);
console.log("after setTimeout()....");

你会在chrome控制台输出看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

可见,异步操作会在将来的某个时间点触发一个函数调用。
AJAX就是典型的异步操作。以代码为例:

request.onreadystatechange = function () {
  if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}

把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。
有没有更好的写法?比如写成这样:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。
测试一下你的浏览器是否支持Promise:

'use strict';
// 直接运行测试:
new Promise(function () {});

简单例子

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败

function test(resolve, reject) {
  var timeOut = Math.random() *2;
  log('set timeout to: ' + timeOut + ' seconds.');
  setTimeout(function() {
   if(timeOut <1) {
     log('call resolve()...');
      resolve('200 OK');
   }
   else {
      log('call reject()...');
      reject('timeout in ' + timeOut + ' seconds.');
   }
  },timeOut*1000)
}

这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

var p1 = new promise(test);
var p2 = p1.then(function(result) {
  console.log("成功"+ result);
});
var p3 = p2.catch(function(reson) {
  console.log("失败"+reson);
});

变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:

// 如果成功,执行这个函数:
p1.then(function (result) {
    console.log('成功:' + result);
});

当test函数执行失败时,我们告诉Promise对象:

p2.catch(function (reason) {
    console.log('失败:' + reason);
});

Promise对象可以串联起来,所以上述代码可以简化为:

new promise(test).then(function(tesult) {
  console.log("成功"+result);
}).catch(function(reason) {
  console.log("失败"+reason);
})

实际测试一下,看看Promise是如何异步执行的:

'use strict';

// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
logging.removeChild(logging.children[logging.children.length - 1]);
}
// 输出log到页面:
function log(s) {
    var p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
};

new Promise(function (resolve, reject) {
    log('start new Promise...');
    var timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}).then(function (r) {
    log('Done: ' + r);
}).catch(function (reason) {
    log('Failed: ' + reason);
});

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:


image.png

链式调用

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。
见证奇迹的时刻:then() 函数会返回一个和原来不同的新的Promise。

job1.then(job2).then(job3).catch(handleError);

其中,job1、job2和job3都是Promise对象。
基本上,每一个 Promise 都代表了链中另一个异步过程的完成。
在过去,要想做多重的异步操作,会导致经典的回调地狱:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:

doSomething().then(function(result) {
  return doSomethingELse(result);
}).then(function(newResult) {
  return doThirdThing(newResult);
}).then(function(finalResult) {
   console.log('Got the final result: ' + finalResult);
}) 
.catch(failureCallback); 
//等价于.then(null, failureCallback);

注意:

  • 定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。
  • (如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。
    下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:
var p = new Promise(function (resolve, reject) {
    log('start new Promise...');
    resolve(2);
});
p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
    consle.log('Got value: ' + result);
});
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
  return new Promise(function(resolve, reject) {
    console.log("calculating "+ input+' x '+input+ "....");
    setTimeout(resolve, 500, input*input) ;
  }) 
}
//0.5秒后返回input+input的计算结果:
function add(input) {
  return new Promise(function(resolve, reject) {
    console.log("calculating "+ input+' +'+input+ "....");
    setTimeout(resolve, 500, input+input);
  }) 
}

Promise.all()

除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
   // 获得一个Array: ['P1', 'P2']
    console.log(results); 
});

Promise.race()

多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。

常见的错误

在编写 Promise 链时,需要注意以下示例中展示的几个错误:

doSomething().then(function(result) {
  doSomethingElse(result);
  // 没有返回 Promise 以及没有必要的嵌套 Promise
}).then(newResult => doThirdThing(newResult));
.then(() => doFourthThing());
// 最后,是没有使用 catch 终止 Promise 调用链,可能导致没有捕获的异常

1.错误是没有正确地将事物相连接。当我们创建新 Promise 但忘记返回它时,会发生这种情况。(同时在执行两个异步而非一个一个的执行)。这意味着 doFourthThing() 不会等待 doSomethingElse() 或 doThirdThing() 完成.
2.不必要地嵌套,实现第一个错误。嵌套还限制了内部错误处理程序的范围,如果是非预期的,可能会导致未捕获的错误。
3.忘记用 catch 终止链。这导致在大多数浏览器中不能终止的 Promise 链里的 rejection。

一个好的经验法则是总是返回或终止 Promise 链,并且一旦你得到一个新的 Promise,返回它。下面是修改后的平面化的代码:

doSomething().then(function(result) {
  return doSomethingElse(result);
}).then(function(newResult) {
  return doThirdThing(newResult);
}).then(function(thirResult) {
  console.log("third"+thirResult);
}).actch(function(error) {
  console.log(error)
});

//或者
doSomething().then(result=>{ return doSomethingElse(result)})
.then(newResult=> {return doThirdThing(newResult)})
.then(()=> {return doFourthThing()})
.catch(error => console.log(error));

上述代码的写法就是具有适当错误处理的简单明确的链式写法。
使用 async/await 可以解决以上大多数错误.

理解了promise 的用法 下一节我们来手写实现一个promise。加油~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342